Skip to content

Linear Structures

Stacks, queues, and deques

Order of removal is the whole idea

Three ways to say who goes next. Same box of data - but the rule you use to add and remove decides everything from browser undo to how a search explores.

An array lets you touch any slot by index. A stack, queue, and deque give that up on purpose - they restrict where you may add and remove, and in exchange every operation is a cheap . The restriction is the feature: it turns out "always take from the top" and "always take from the front" are exactly the disciplines that undo, scheduling, and graph traversal need.

Three access rules, one picture

A stack is a pile of plates: you add and remove at the same end, the top - last in, first out (LIFO). A queue is a line at a counter: you join at the rear and are served from the front - first in, first out (FIFO). A deque ("deck", double-ended queue) unlocks both ends, so it can act as either one.

Two disciplines: LIFO and FIFO

Each structure exposes a tiny, fixed vocabulary. A stack has push (add on top), pop (remove the top), and peek (look at the top without removing). A queue has enqueue (add at the rear), dequeue (remove from the front), and front (peek at who's next). A deque simply offers both ends. None of them let you reach into the middle - that's the whole point.

StructureAddRemovePeekOrderCost
Stackpush → toppop ← toppeek topLIFOO(1)
Queueenqueue → reardequeue ← frontfrontFIFOO(1)
Dequeboth endsboth endsboth endseitherO(1)

Under the hood you can build any of them two ways. A linked list gives ends for free - splice a node on or off. An array is faster and cache-friendly for a stack (grow and shrink the tail), but a naïve array queue is a trap: removing the front by shifting everything left is . The fix - a circular buffer - is below.

See the disciplines move

Push and pop the stack and watch values enter and leave the same top end. Enqueue and dequeue the queue and watch values enter at the rear but leave from the opposite front end. Then try the balanced-parentheses checker: type brackets and step through as a stack pushes each opener and pops it when its partner closer arrives - the classic proof that a stack is the right tool for matching.

Access-discipline animator push/pop · enqueue/dequeue · bracket check
StackLIFO
3
2
1

add & remove at the top - same end

size 3 · top 3

QueueFIFO
1
2
3

front ← … ← rear - enter rear, leave front

size 3 · front 1 · rear 3

Balanced-parentheses checkera stack at work
try:
{
[
(
)
]
}
stack
empty

Start with an empty stack, then scan left to right.

Why matching needs a stack

Every closer must pair with the most recent unmatched opener - the newest one, closest to the top. That "newest first" rule is LIFO, so a stack answers it in one glance at the top. The same shape powers undo (the last action you took is the first to reverse), expression evaluation (operators and parentheses nest), and the call stack (the most recent function call returns first). Whenever "reverse the most recent thing," reach for a stack.

The circular buffer: an O(1) queue in a fixed array

Keep two indices into an array of capacity : front (where the next dequeue happens) and rear (where the next enqueue lands). Instead of shifting elements, you just advance an index, wrapping around with modulo:
The array's slots form a ring: index wraps to . Nothing ever moves, so both operations are instead of the you'd pay to shift a whole array left on every dequeue. The queue is empty when and full when advancing would collide with .

In code

A stack is just a list you only touch at the tail. The bracket checker from the demo is eight lines. And in practice you rarely hand-roll a queue - a deque gives at both ends, so it is a stack, a queue, or a deque depending on which methods you call.

def balanced(s):
    pairs = {')': '(', ']': '[', '}': '{'}
    stack = []
    for ch in s:
        if ch in '([{':                       # opener -> push it
            stack.append(ch)
        elif ch in ')]}':                     # closer -> must match the top
            if not stack or stack.pop() != pairs[ch]:
                return False                    # nothing to match, or wrong pair
    return not stack                          # balanced iff nothing left unclosed

from collections import deque            # O(1) at BOTH ends
q = deque()
q.append(x)          # enqueue at rear
q.popleft()          # dequeue from front   -> FIFO (a queue)
q.append(x); q.pop() # push then pop at rear -> LIFO (a stack)
The bracket checker is eight lines; a deque is O(1) at both ends.

Two ways a queue goes wrong

1. The shifting trap. Building a queue as a plain array and calling list.pop(0) (or shifting everything left) makes every dequeue - a hot loop silently degrades to . Use a circular buffer or a deque. 2. Overflow & underflow. A fixed-capacity circular buffer must reject enqueue when full and dequeue when empty; forget the checks and you overwrite live data or read a stale slot. Stacks have the same rule - pop on empty is a bug, not a zero.

Check yourself

You push 1, then 2, then 3 onto a stack, then pop once. Which value comes out?

Why does a circular buffer make dequeue O(1) instead of O(n)?

Recall: which end(s) does each structure use, and what order does that produce?

Tie the access rule to the ordering it forces. Try to state it, then check.

Lock it in

  • A stack, queue, and deque trade random access for a cheap O(1) add and remove; the restriction on where you touch the data is the whole feature.
  • Stack = same end (LIFO), queue = opposite ends (FIFO), deque = both ends (either).
  • Matching, undo, expression evaluation, and the call stack are all "reverse the most recent thing" - reach for a stack.
  • A circular buffer (or a deque) keeps a queue O(1) by advancing an index instead of shifting cells; a plain-array queue degrades to O(n) per dequeue.

Primary source

Watch these disciplines animate on your own input: VisuAlgo - list / stack / queue steps through push/pop and enqueue/dequeue with the ring buffer drawn explicitly, and the USF Data Structure Visualizations (David Galles) include stand-alone stack, queue, and deque players you can single-step.

Where this reappears

Want to see how a priority queue (a heap) bends FIFO into "highest-priority-first," or how to build a queue from two stacks? These access rules are everywhere in both paths: BFS explores a graph with a queue as its frontier, the call stack is a stack, and beam-search decoding in a language model keeps its best candidate sequences in a priority queue - a smarter queue that serves the most promising beam next.