Stacks and Queues, Shown Doing the Jobs They Exist For

✓ numbers produced by the code on this page · Published 2026-08-20

Stacks and queues are the two structures most often taught purely as definitions. Last-in-first-out; first-in-first-out; here is a picture of some plates. It is accurate and it explains nothing, because knowing the rule tells you nothing about why anybody wanted it.

Both rules are worth having for the same reason: each makes exactly one question cheap. A stack makes what was the most recent thing? instant. A queue makes what has been waiting longest? instant. The way to see why that matters is to watch each one do a job.

A stack, matching brackets

Every closing bracket has exactly one candidate partner: the most recent opener that is still unclosed. That is a stack’s only trick, which makes this the job stacks were born for.

Match brackets with a stack the top is the only place that matters

A stack answers exactly one question quickly: what was the most recent thing? That is the question bracket matching asks on every closing bracket, because the only opener a closer could possibly belong to is the most recent unclosed one.

Scanning ([)] pushes ( then [, and then meets ) at position 2 — where the top of the stack is [, not (. Counting brackets would accept this string, since it has two openers and two closers; a stack rejects it, because it remembers the order. Scanning (() gets all the way to the end and fails differently: reached the end with 1 bracket still open.

Step through each input. The highlighted stack entry is the top — the only entry a closing bracket ever looks at.

Run ([)] and watch where it fails. This is the case worth understanding, because a simpler approach accepts it: counting. Two openers, two closers, balanced — and wrong. Counting knows how many brackets there are and nothing about which is inside which. The stack fails at position 2, and it fails for a reason it can state: the ) arrived while [ was still open.

Then run ((). It fails differently — nothing mismatched at all, the scan simply ran out of input with one bracket still on the stack. Both are unbalanced strings; only a structure that remembers order can tell you which kind of unbalanced.

A queue whose values never move

Here is the problem the array-versus-linked-list comparison leaves open. Removing from the front of an array shifts everything behind it forward, which is linear work on every single removal — and taking from the front is the definition of a queue. So how is a fast array-backed queue possible?

By not moving the data. Keep two indices instead, and let them wrap around the end.

A queue that never shifts move the indices, not the data

Taking an item from the front of an ordinary array means shifting everything behind it forward one slot. A ring buffer avoids that by never moving the values at all: it keeps two indices, head for where the next item comes out and tail for where the next one goes in, and lets both wrap around the end of a fixed array.

After filling 5 slots of a 6-slot buffer and taking 3 items out, head sits at index 3 and tail at 5. Two more additions push tail to index 1 — it has wrapped past the end and back to the start, writing into slots the earlier items have already vacated. Every enqueue and every dequeue is a constant amount of work no matter how full the buffer is, because nothing is ever shifted.

Add several items, take a few, then keep adding. Watch tail roll past the end back to index 0 while the values sit exactly where they were written.

Add four, remove three, then add more, and tail wraps to 0 and starts writing into slots the earlier items have vacated. The array is being used as a circle. Nothing is ever shifted, so both operations are constant work regardless of how many items are held — which is exactly what the plain-array version failed to deliver.

This is not a toy. Ring buffers are how audio pipelines, network stacks, keyboard input and log collectors move data between a producer and a consumer, precisely because the cost per item does not grow with the backlog.

Both, as programs

python
PAIRS = {")": "(", "]": "[", "}": "{"}


def check(text):
    """Returns None if balanced, or (position, reason) for the first thing that is wrong."""
    stack = []
    for i, ch in enumerate(text):
        if ch in "([{":
            stack.append((ch, i))
        elif ch in PAIRS:
            if not stack:
                return i, f'"{ch}" closes nothing'
            top, at = stack.pop()
            if top != PAIRS[ch]:
                return i, f'"{ch}" does not match "{top}" opened at {at}'
    if stack:
        return len(text), f"{len(stack)} bracket(s) never closed"
    return None


for text in ["([]{})", "([)]", "(()", "sum(a[i], b[j])"]:
    problem = check(text)
    if problem is None:
        print(f"{text!r:20} balanced")
    else:
        at, why = problem
        print(f"{text!r:20} FAILS at {at}: {why}")

print()

SIZE = 4
slots, head, tail, count, value = [None] * SIZE, 0, 0, 0, 1
print(f"{'operation':<10} {'head':>4} {'tail':>4} {'held':>4}   slots")
for op in ["add", "add", "add", "take", "take", "add", "add", "take", "add"]:
    if op == "add" and count < SIZE:
        slots[tail] = value
        tail = (tail + 1) % SIZE
        count += 1
        value += 1
    elif op == "take" and count > 0:
        slots[head] = None
        head = (head + 1) % SIZE
        count -= 1
    shown = " ".join("." if s is None else str(s) for s in slots)
    print(f"{op:<10} {head:>4} {tail:>4} {count:>4}   {shown}")
Output
'([]{})'             balanced
'([)]'               FAILS at 2: ")" does not match "[" opened at 1
'(()'                FAILS at 3: 1 bracket(s) never closed
'sum(a[i], b[j])'    balanced

operation  head tail held   slots
add           0    1    1   1 . . .
add           0    2    2   1 2 . .
add           0    3    3   1 2 3 .
take          1    3    2   . 2 3 .
take          2    3    1   . . 3 .
add           2    0    2   . . 3 4
add           2    1    3   5 . 3 4
take          3    1    2   5 . . 4
add           3    2    3   5 6 . 4

Read the slots column down the page. By the seventh row it says 5 . 3 4 — value 5 is sitting at index 0 while 3 and 4 are still at indices 2 and 3, exactly where they were written. The queue’s order is 3, 4, 5, and the array’s order is nothing of the sort. That gap between the logical sequence and the physical layout is the whole idea, and it is why the operation costs the same whether the buffer holds two items or two thousand.

The connection worth carrying away

Neither structure stores anything an array could not. What each provides is a restriction, and the restriction is what makes one question cheap. That is a pattern rather than a pair of facts:

Choosing a structure is choosing which question you want to be free.

What this page simplifies