InterviewKata
Data structure 7 of 12

Queues and deques

A queue is a line: join at the back, leave from the front. The first one in is the first one out. A deque (double-ended queue) lets you add and remove at both ends, all in O(1).

A 2-minute conversation: Sam asks, Priya explains
0:00 / 2:11

1. What it is

The queue at a bakery. New customers join at the back; the person at the front is served next. Nobody pushes in and nobody leaves from the middle. Whoever has waited longest goes first.

A queue follows FIFO: first in, first out. Its operations, all O(1):

A deque (say "deck") adds the mirror operations: push at the front and pop from the back. It can act as a queue, a stack, or both at once.

2. How it sits in memory

A plain list makes a poor queue: removing from the front shifts every other item left, so each pop(0) is O(n). A real queue avoids shifting by moving the front instead of the items: a ring buffer keeps two indices and wraps around the end of its array.

Python's collections.deque, Java's ArrayDeque and similar classes do this for you, so both ends are O(1).

3. Operations and what they cost

OperationPython dequeCostWith a plain list
Add at the backq.append(x)O(1)O(1)
Remove from the frontq.popleft()O(1)O(n) with pop(0)
Add at the frontq.appendleft(x)O(1)O(n)
Remove from the backq.pop()O(1)O(1)
Peek front, backq[0], q[-1]O(1)O(1)
Read the middleq[i]O(n)O(1)
When it mattersBreadth-first search pops from the front once per node. With list.pop(0) a big search becomes O(n²); with a deque it stays O(n).

4. In Python, TypeScript and Java

Use the language's deque; never shift a plain array from the front in a loop.

from collections import deque
    q = deque()
    q.append("ana")        # join at the back
    q.append("ben")
    front = q[0]           # peek: "ana"
    first = q.popleft()    # "ana" leaves from the front
    q.appendleft("vip")    # a deque can add at the front too
    last = q.pop()         # and remove from the back

5. Core operations, step by step

A queue by hand first, then the two ways queues are used most.

A · A queue by hand (a ring buffer)

What it doesKeeps the items in a fixed array with a head index and a size; the back wraps around to the start.
Picture itA round table with numbered seats: the 'front' badge moves round the table instead of everyone changing chairs.
CostO(1) per enqueue and dequeue: nothing ever shifts.

The code. The modulo (%) makes the index wrap from the last slot back to 0.

class RingQueue:
    def __init__(self, capacity):
        self.slots = [None] * capacity
        self.head = 0
        self.size = 0

    def enqueue(self, x):
        tail = (self.head + self.size) % len(self.slots)
        self.slots[tail] = x
        self.size += 1

    def dequeue(self):
        x = self.slots[self.head]
        self.head = (self.head + 1) % len(self.slots)
        self.size -= 1
        return x

Line by line.

LineWhat it doesWhy
tail = (self.head + self.size) % len(self.slots)The first free slot after the last item.% wraps past the end back to slot 0.
self.head = (self.head + 1) % len(self.slots)The front moves one seat on.Moving an index is O(1); shifting items would be O(n).

Watch it run. A 3-slot queue: enqueue 1, 2; dequeue; enqueue 3, 4 (4 wraps round to slot 0); dequeue three times.

Watch out for:

  • Forgetting the modulo. The index runs past the end of the array instead of wrapping.

B · Process in arrival order

What it doesServes whoever has waited longest; new arrivals join the back while serving goes on.
Picture itThe bakery: serve the front customer; anyone who walks in joins the back.
CostO(n) for n items.

The code. Each job may create follow-up jobs; they wait their turn behind everything already queued.

from collections import deque

def run_jobs(jobs):
    q = deque(jobs)
    done = []
    while q:
        job = q.popleft()
        done.append(job)
        if job.endswith("!"):
            q.append(job[:-1] + "-retry")
    return done

Line by line.

LineWhat it doesWhy
job = q.popleft()Serve the front.O(1) on a deque.
q.append(...)A follow-up job joins the back.It waits behind c, which arrived earlier: fair, first come first served.

Watch it run. Jobs a, b! (needs a retry) and c.

Watch out for:

  • list.pop(0) in the loop. O(n) per pop. Use a deque (or a head index in TypeScript).

C · Keep only the recent ones (a sliding window of time)

What it doesAdds each new event at the back and drops events at the front that are too old.
Picture itA queue for a ride that closes its gate: people who've waited longer than 10 minutes give up and leave from the front.
CostO(1) amortised per event: each event is added once and removed once.

The code. Old items are always at the front, because they arrived first.

from collections import deque

def recent_counts(times, window):
    q = deque()
    out = []
    for t in times:
        q.append(t)
        while q[0] < t - window:
            q.popleft()
        out.append(len(q))
    return out

Line by line.

LineWhat it doesWhy
q.append(t)The newest event joins the back.
while q[0] < t - windowIs the oldest event too old?Times only grow, so the oldest is always at the front.
q.popleft()It leaves from the front.Each event leaves at most once: O(1) amortised.

Watch it run. Events at times 1, 100, 3001 and 3002, keeping the last 3000 ms.

Watch out for:

  • Re-counting every event each time. That's O(n) per call; the queue drops old events once.

6. How to remember it

Memory hook: "The bakery queue"Join at the back, leave from the front, nobody pushes in. A deque is a queue with a door at both ends.

Picture

A line of customers with a ticket machine at the back and the counter at the front.

Chant

"Append at the back, popleft at the front, never pop(0)."

Key rule

Oldest first? Queue. Newest first? Stack. Both ends? Deque.

7. Problems solved with it

Two easy problems that are queues in disguise.

LC 933 · Number of Recent Calls

The problemEach call to ping(t) (times strictly increasing) returns how many pings happened in [t − 3000, t].
What it doesOperation C, as a class.
CostO(1) amortised per ping.

The code. The deque holds exactly the pings still inside the window.

from collections import deque

class RecentCounter:
    def __init__(self):
        self.q = deque()

    def ping(self, t):
        self.q.append(t)
        while self.q[0] < t - 3000:
            self.q.popleft()
        return len(self.q)

Line by line.

LineWhat it doesWhy
while self.q[0] < t - 3000Drop pings older than the window.

Watch it run. Pings at 1, 100, 3001, 3002.

Watch out for:

  • Keeping every ping forever and counting. Memory and time grow without bound.

LC 1700 · Number of Students Unable to Eat Lunch

The problemStudents queue for sandwiches (0 or 1); the stack of sandwiches is served from the top. A student takes the top sandwich if it's their type, otherwise goes to the back of the queue. Return how many can't eat.
What it doesSimulates the queue until nobody in it wants the top sandwich.
CostO(n²) worst case for the simulation; fine for n ≤ 100.

The code. Stop when everyone has been sent to the back once in a row without eating.

from collections import deque

def count_students(students, sandwiches):
    q = deque(students)
    top = 0
    misses = 0
    while q and misses < len(q):
        s = q.popleft()
        if s == sandwiches[top]:
            top += 1
            misses = 0
        else:
            q.append(s)
            misses += 1
    return len(q)

Line by line.

LineWhat it doesWhy
s = q.popleft()The student at the front steps up.
q.append(s)Wrong sandwich: back of the queue.The queue rotates.
misses < len(q)Stop after a full round with nobody eating.Otherwise the loop would rotate forever.

Watch it run. Students [1, 1, 0, 0], sandwiches [0, 1, 0, 1] (top first).

Watch out for:

  • No stopping rule. When nobody wants the top sandwich, the queue rotates forever.

More practice, solved on the pattern pages

#ProblemLevelWhere
637Average of Levels in Binary TreeEasySolved step by step on 12 · Breadth-First Search (BFS)
102Binary Tree Level Order TraversalMediumSolved step by step on 10 · Binary Tree Traversal
994Rotting OrangesMediumSolved step by step on 12 · Breadth-First Search (BFS)
239Sliding Window MaximumHardSolved step by step on 03 · Sliding Window

8. Common mistakes

list.pop(0) and array.shift()Both shift every element: O(n) per call. Use collections.deque, Java's ArrayDeque, or a head index in TypeScript.
Popping the wrong endA queue takes from the front (popleft); pop() takes from the back and turns your queue into a stack.
Reading an empty queueq[0] or popleft() on an empty deque raises IndexError. Check while q:.
Indexing the middle of a dequeq[i] is O(n) for a deque. If you need random access, you need a list.

9. Self-check quiz

Enqueue 1, enqueue 2, enqueue 3, then dequeue. What comes out?

First in, first out.

Why is list.pop(0) slow?

Arrays keep items in consecutive slots, so removing the first moves all the rest.

Best way to build a queue in Python?

O(1) at both ends.

You need to add and remove at both ends quickly. Which structure?

A double-ended queue: O(1) at the front and the back.

10. Where it shows up next

Next structure: 08 · Heaps and priority queues, a queue where the most important item always goes first.