computer-science/stacks-and-queues-why-undo-takes-the-newest-item-and-a-printer-takes-t.md

Stacks and queues: why undo takes the newest item and a printer takes the oldest

Computer science · 5 min read ·

Press undo and the last thing you typed disappears first. Send documents to a shared printer and the first one is printed first. Both are lists of items waiting their turn, and the only difference is which item leaves next.

This article explains stacks (last in, first out) and queues (first in, first out) in plain words. You add items to both at once and watch them hand back different ones, check a bracket matcher in Python, and see why a deque beats a list for queues.

Two stacks of lettered boxes: a stack with C on top and a queue with A at the front

Open a text editor, type three words, and press undo. The last thing you typed disappears first. Now think about a printer shared by an office: the first document sent is the first one printed. Both are lists of items waiting their turn, and the only difference is which item comes out next. That one difference has names: a stack and a queue.

Two lists, one rule each

A data structure is just a way of arranging items so that certain jobs are easy. A stack and a queue both hold items in order, and both allow only two basic moves: put an item in, and take an item out. They differ in the rule for taking.

A stack is last in, first out, often shortened to LIFO. Think of plates in a cafeteria: you add a plate on top and you take a plate from the top. Putting an item in is called push and taking it out is called pop.

A queue is first in, first out, or FIFO. It works like a line at a shop: people join at the back and the person at the front is served first. Joining is often called enqueue and leaving the front is dequeue.

Try it: same items, different order

Press Add item a few times. Each new item goes into both containers. Then press Take one and watch which item each container gives back.

If you added A, B and C and then took one, the stack gave you C and the queue gave you A. Nothing else changed. The items, their order of arrival and the number of moves are identical; only the rule for which one leaves is different.

Where stacks show up

Anything that needs to undo the most recent thing first is a stack. Undo in an editor keeps your changes in a stack. The Back button in a browser works the same way: each page you visit is pushed, and Back pops the latest one. Even your program's own record of unfinished function calls is called the call stack, because the function that started most recently must finish first.

Stacks are also how a program checks that brackets match. Read the text from left to right. Push every opening bracket. When you meet a closing bracket, pop the top and see whether it is the matching opener. Here is that idea in Python:

def balanced(text):
    pairs = {")": "(", "]": "[", "}": "{"}
    stack = []
    for ch in text:
        if ch in "([{":
            stack.append(ch)          # push
        elif ch in pairs:
            if not stack or stack.pop() != pairs[ch]:
                return False          # nothing to match, or wrong kind
    return not stack                  # anything left over is unmatched

print(balanced("(a[b]{c})"))   # True
print(balanced("(a[b)]"))      # False
print(balanced("(("))          # False

I ran this code and it printed exactly True, False, False. In the second example the ) arrives while [ is on top, so the match fails. A stack catches that mistake because the most recent opener must be closed first.

Where queues show up

Anything that should be handled fairly, in arrival order is a queue: print jobs, customers waiting for support, tasks waiting for a worker program to pick them up. The breadth-first search that finds a short path through a maze also uses a queue, so that nearby squares are explored before far-away ones.

Using them in Python

A Python list makes a good stack, because append and pop() both work at the end of the list:

stack = []
stack.append("a")
stack.append("b")
stack.append("c")
print(stack.pop())   # c
print(stack)         # ['a', 'b']

For a queue, Python provides collections.deque (pronounced "deck", short for double-ended queue). It lets you add at one end and remove from the other quickly:

from collections import deque

queue = deque()
queue.append("a")
queue.append("b")
queue.append("c")
print(queue.popleft())  # a
print(queue)            # deque(['b', 'c'])

You could use a list as a queue with pop(0), and it works, but it is slower for long queues. Removing the first item of a list makes Python shift every remaining item one place to the left. I timed 2,000 remove-then-add rounds on this machine: with 10,000 items a list took about 3 milliseconds and a deque about 0.2. With 100,000 items the list took about 36 milliseconds, roughly ten times longer, while the deque stayed at about 0.2. Exact numbers will differ on your computer, but the pattern holds: the list gets slower as it grows, the deque does not.

So what for your code?

When you need to hold things for later, ask one question first: which item should come out next? If the answer is "the newest one", reach for a stack. If it is "the one that has waited longest", use a queue. Picking the right one often removes the need for any clever sorting or searching, because the order you want is built into the structure.

Many languages have these ready to use: JavaScript arrays have push and pop for stacks, and Python offers the two shown above. Next time you press undo or watch a progress bar line up jobs, you will know which of the two is doing the work.

$ ls computer-science/

see all →
computer-science/merge-sort-how-splitting-a-list-in-half-again-and-again-sorts-it-with.md Merge sort: how splitting a list in half again and again sorts it with far less work How do you sort 10,000 numbers without making millions of comparisons? Merge sort never sorts a big pile. It splits the list until every… 5 min read · 27 Oct computer-science/hash-tables-how-a-computer-finds-one-name-among-thousands-without-chec.md Hash tables: how a computer finds one name among thousands without checking them all Your phone finds one contact among thousands without reading them all. The trick is a hash table: a row of numbered slots and a small… 6 min read · 24 Oct computer-science/breadth-first-search-how-a-maze-solver-finds-the-shortest-way-out.md Breadth-first search: how a maze solver finds the shortest way out How does a maze solver, or a map app, find the shortest route and not just any route? The answer is breadth-first search: explore every… 6 min read · 17 Oct computer-science/binary-search-how-to-find-one-number-in-a-million-with-20-guesses.md Binary search: how to find one number in a million with 20 guesses Guessing a number between 1 and 1,000,000 sounds like it could take a million tries. With binary search it takes at most 20. Step through a… 4 min read · 14 Oct computer-science/big-o-how-to-tell-if-your-code-will-survive-1-000-times-more-data.md Big O: how to tell if your code will survive 1,000 times more data Your code works on 10 items, then crawls on 10,000. Big O is the short way programmers describe how the number of steps grows as the input… 4 min read · 11 Oct computer-science/recursion-why-fib-30-makes-2-7-million-calls-and-how-a-notebook-fixes.md Recursion: why fib(30) makes 2.7 million calls, and how a notebook fixes it A function that calls itself sounds like an endless loop, but it works when every call gets a smaller problem and one case is simple enough… 5 min read · 8 Oct