◐ ai-teacher

🧩 Data Structures & Algorithms · Chapter 7

Queues and deques

The print jobs on an office printer, the messages between two services, and breadth-first search through a map all need the same promise: whoever arrived first is served first.

Study this lesson free in the app →

The basics

A queue is a collection where items join at the back and leave from the front: first in, first out — FIFO. A deque ("deck", double-ended queue) lets you add and remove at both ends.

The problem it solves: whenever work arrives faster than it can be handled — print jobs, customer requests, messages, cells to explore in a maze — you need to hold it and process it fairly, in arrival order. A queue does that with O(1) enqueue and dequeue.

The mental model: a line at a bakery counter. New customers join the end; the person at the front is served next. Nobody jumps the line, and nobody waits forever.

Words you must know 5

enqueue
add at the back. O(1).
dequeue
remove from the front. O(1) — if the structure is right.
FIFO
first in, first out (the opposite of a stack's LIFO).
Deque
O(1) at both ends: append, appendleft, pop, popleft.
Circular buffer
a fixed-size array whose front and back indices wrap around, giving O(1) operations with no shifting.

In Python, use collections.deque. A plain list is the wrong tool: list.pop(0) shifts every remaining item, O(n). Space is O(n) for n items.

Explained

Ten people send documents to one printer within a minute. The printer can handle one at a time.

If it printed the most recent job first (a stack), the first person might wait all afternoon while newer jobs keep jumping ahead.

So the printer keeps a queue: first sent, first printed. Fair, predictable, and every add and remove takes constant time.

The bakery line and the ferry ramp

At a bakery, customers take their place at the end of the line and the baker serves whoever is at the front. Someone who arrives at 8:01 is always served before someone who arrives at 8:02. That's a queue.

Now picture a small car ferry with one ramp at each end. Cars can drive on at the back or, if the crew decides, squeeze on at the front; at the far shore they can drive off either end. That's a deque — both ends are open.

What neither allows is pulling a car out of the middle of the line. Both structures are fast precisely because they only ever touch the ends.

Precisely

| Operation | deque | list | | --- | --- | --- | | add at back — append(x) | O(1) | O(1) amortised | | remove at front — popleft() / pop(0) | O(1) | O(n) | | add at front — appendleft(x) / insert(0, x) | O(1) | O(n) | | remove at back — pop() | O(1) | O(1) | | index in the middle — d[i] | O(n) | O(1) |

How O(1) at the front is possible:

For threads handing work to each other, queue.Queue wraps this with locks and blocking get().

Enqueue at the back, dequeue from the front

3, 7 and 1 join in that order. The first dequeue removes 3 — the oldest, not the newest. 9 joins at the back; the next dequeue removes 7. After 4 joins, the queue reads 1, 9, 4 from front to back. Every operation touches only an end: O(1).

A ticket counter: everyone served in order

Tickets 101–103 arrive, two are served, two more arrive, then the counter catches up. Served order: 101, 102, 103, 104, 105 — exactly the arrival order, however arrivals and service interleave. Run the same operations as a stack and the order would be 103, 102, 105, 104, 101: ticket 101 waits until the very end.

deque in practice, and BFS

from collections import deque

q = deque()
q.append("job-1")            # enqueue   O(1)
q.append("job-2")
q.popleft()                  # dequeue   O(1)  -> 'job-1'

last3 = deque(maxlen=3)      # keeps only the newest 3
for reading in [5, 6, 7, 8]:
    last3.append(reading)    # deque([6, 7, 8]) — 5 fell off the front

def bfs_distance(graph, start):
    dist = {start: 0}
    q = deque([start])
    while q:
        node = q.popleft()               # nearest unexplored first
        for nxt in graph[node]:
            if nxt not in dist:
                dist[nxt] = dist[node] + 1
                q.append(nxt)
    return dist                          # O(V + E) time, O(V) space

city = {"Home": ["Cafe", "Park"], "Cafe": ["Home", "Library"],
        "Park": ["Home", "Library", "Museum"],
        "Library": ["Cafe", "Park", "Station"],
        "Museum": ["Park"], "Station": ["Library"]}
bfs_distance(city, "Home")
# {'Home': 0, 'Cafe': 1, 'Park': 1, 'Library': 2, 'Museum': 2, 'Station': 3}

Because the queue releases nodes in the order they were found, every node at distance 1 is processed before any at distance 2 — so the first time BFS reaches a node is along a shortest path.

Queues between machines

The same FIFO idea, stretched across a network, is how large systems stay up:

The question to ask in a design round: what happens when the queue fills up?

Gotchas

Words to know

QueueA first-in, first-out collection: add at the back, remove from the front.
FIFOFirst in, first out — items leave in the order they arrived.
DequeDouble-ended queue: O(1) insertion and removal at both ends.
Circular bufferA fixed-size array used as a queue, with head and tail indices that wrap around using % capacity.
Breadth-first search (BFS)Graph traversal that explores nodes in order of distance using a queue; finds shortest paths in unweighted graphs.
Back-pressureSlowing or refusing producers when a bounded queue is full, so a fast producer can't overwhelm a slow consumer.
Priority queueServes the highest-priority (e.g. smallest) item first rather than the earliest; usually a heap.

In two minutes

Test yourself

1. What does FIFO mean for a queue?

  1. The newest item leaves first
  2. The smallest item leaves first
  3. The oldest item leaves first
  4. Any item can leave
Show the answer

The oldest item leaves first — First in, first out. Newest-first is a stack (LIFO); smallest-first is a priority queue.

2. Which Python operation removes from the front of a queue in O(1)?

  1. `list.pop(0)`
  2. `deque.popleft()`
  3. `list.pop()`
  4. `deque.pop()`
Show the answer

`deque.popleft()` — popleft on a deque is O(1). list.pop(0) also removes the front but shifts everything, O(n). pop() removes from the back — that's stack behaviour.

3. Starting empty: enqueue 3, enqueue 7, enqueue 1, dequeue, enqueue 9, dequeue, enqueue 4. What is the queue, front to back?

  1. [3, 7, 4]
  2. [9, 1, 4]
  3. [7, 1, 9, 4]
  4. [1, 9, 4]
Show the answer

[1, 9, 4] — The dequeues remove 3, then 7 — always the oldest. What remains, in arrival order, is 1, 9, 4.

4. You need the fewest moves for a knight between two squares on a chessboard (every move costs the same). Which traversal and structure?

  1. BFS with a queue
  2. DFS with a stack
  3. Binary search
  4. A hash map alone
Show the answer

BFS with a queue — With equal-cost moves, BFS explores all squares 1 move away, then 2, and so on, so the first time it reaches the target is via a shortest route. DFS can wander down a long path first.

5. A circular buffer has capacity 5. Its front is at index 3 and it holds 4 items. At which index is the next item written?

  1. 7
  2. 4
  3. 2
  4. 0
Show the answer

2 — tail = (head + size) % capacity = (3 + 4) % 5 = 2. The items occupy 3, 4, 0, 1, so slot 2 is the only free one. 7 forgets to wrap.

6. You want to keep only the 100 most recent log lines in memory as new ones stream in. Simplest correct tool?

  1. A list, deleting index 0 when it passes 100
  2. `deque(maxlen=100)`
  3. A set
  4. A stack
Show the answer

`deque(maxlen=100)` — A bounded deque drops the oldest item automatically in O(1) on every append. Deleting list[0] works but is O(n) per line.

There are 9 questions, 4 puzzles and 5 recall cards for this lesson in the app — the recall cards come back on later days, just before you would forget.

Learn it so it stays. Read it, test yourself, then recall it on two different days. The app schedules that for you, free.

Start this lesson →