Skip to content

Beginner: foundations · Stacks, queues and linked lists · topic 17 of 61 · 35 min

Queues and deques

Explanation

A queue adds at the back and removes from the front: first in, first out (FIFO). A Python list is a poor queue because pop(0) shifts every item, O(n). collections.deque adds and removes at both ends in O(1). A circular queue reuses one fixed array by wrapping its indexes with % capacity, and in JavaScript you avoid the O(n) shift() by moving a head index.

First in, first out

A queue adds items at the back and removes them from the front: first in, first out, or FIFO. The two main operations are enqueue (join at the back) and dequeue (leave from the front). Queues model fair waiting: print jobs, support chats, orders waiting for a kitchen, and the order in which breadth-first search visits a graph (Module 9).

Think of it like this: A railway ticket counter: people join at the end of the line, and the clerk serves whoever is at the front. Nobody who arrived later is served earlier.

Why list.pop(0) is slow

A Python list keeps its items side by side. Removing the first item means every other item shifts one place left, which is O(n). A loop that dequeues n items this way costs O(n²) in total. JavaScript's arr.shift() moves the remaining items in the same way, so treat it as O(n) too.

collections.deque

deque (said "deck", short for double-ended queue) is built for both ends. append and appendleft, pop and popleft are all O(1). In CPython it is a doubly linked list of fixed-size blocks, so it never shifts items. Reading q[0] or q[-1] is O(1), but indexing in the middle is O(n). deque(maxlen=k) keeps only the newest k items and drops the oldest automatically.

python
from collections import deque

q = deque()
q.append("token 1")
q.append("token 2")
q.append("token 3")
print(q.popleft())    # Output: token 1
print(q)              # Output: deque(['token 2', 'token 3'])
q.appendleft("urgent")
print(q[0], len(q))   # Output: urgent 3

recent = deque(maxlen=3)
for x in range(1, 6):
    recent.append(x)
print(recent)         # Output: deque([3, 4, 5], maxlen=3)
Operationlistdeque
Add at the backappend: O(1) amortisedappend: O(1)
Remove from the frontpop(0): O(n)popleft(): O(1)
Add at the frontinsert(0, x): O(n)appendleft(x): O(1)
Read an item in the middlea[i]: O(1)q[i]: O(n)

A circular queue on a fixed array

Some systems cannot grow their memory: a device driver, a game's input buffer, or a logger that keeps the last 1,000 events. A circular queue, also called a ring buffer, uses one array of fixed capacity and two numbers: head, the index of the front item, and count, how many items it holds. The next free slot at the back is (head + count) % capacity. The % makes indexes wrap past the end back to 0, so slots freed at the front are reused. Every operation is O(1), and the space is O(capacity).

StepSlotsheadcount
start[ _, _, _ ]00
enqueue 1, 2, 3[ 1, 2, 3 ]03 (full)
dequeue → 1[ _, 2, 3 ]12
enqueue 4 at (1 + 2) % 3 = 0[ 4, 2, 3 ]13 (full)
dequeue → 2[ 4, _, 3 ]22

Keeping count, and not only head and tail indexes, makes "empty" (count == 0) and "full" (count == capacity) easy to tell apart; with two indexes alone, both cases can look like head == tail.

Where a double-ended queue helps

  • Sliding window maximum: a deque of indexes that you trim from both ends (Topic 3).
  • Keeping only the latest k items with deque(maxlen=k): recent searches, or recent request times in a rate limiter.
  • Checking a palindrome by removing one item from each end and comparing.
  • Breadth-first search, where popleft() takes the next node to visit (Module 9).

A note for JavaScript

JavaScript has no built-in queue type. arr.shift() removes the first item but is O(n) in general. Two simple fixes: keep a head index into the array and move it forward instead of shifting (and now and then drop the used part with slice so memory is freed), or build a small linked list with head and tail references (Topic 4). The task below uses the index idea.

Common mistakes

  • Using list.pop(0) or arr.shift() in a loop over n items: O(n²). Fix: deque.popleft() in Python, a head index in JavaScript.
  • Indexing a deque in the middle inside a loop, which is O(n) each time. Fix: use a list when you need random access.
  • Forgetting % capacity in a circular queue, so the index runs off the end. Fix: compute every slot index modulo capacity.
  • Mixing up empty and full when head equals tail. Fix: keep a count (or leave one slot always unused).
  • Using queue.Queue in algorithm code. It is made for passing work between threads and adds locking. Fix: use collections.deque.

Need help?

Stuck on a word or an idea? Search this course, or every course, and jump straight to the explanation or a worked question.

Try:

Watch the lesson

Queues and deques · 1/8

Queues and deques

This lesson is about queues: first in, first out, like a ticket counter.

Prefer a video by a teacher? Find free videos on “queue deque circular queue ring buffer python collections deque explained” on YouTube ↗

In real life: A rate limiter for an API

Public APIs, such as those that check the status of a train or a parcel, usually limit how many requests one user can make, for example 5 per second. A simple limiter keeps a deque of the user's recent request times. Each new request first removes times older than the window from the front, then checks how many are left. Old times leave from the front and new ones join at the back, so both ends are O(1).

python
from collections import deque

times = deque()
LIMIT, WINDOW = 3, 1000          # 3 requests per 1000 ms

def allow(t):
    while times and times[0] <= t - WINDOW:
        times.popleft()
    if len(times) < LIMIT:
        times.append(t)
        return True
    return False

print([allow(t) for t in [0, 100, 200, 300, 1050, 1150]])
Shows [True, True, True, False, True, True]: the 4th request comes too soon; by 1050 ms the first one has expired.

Practice (0/3 done)

Task 1 · Code · Python

Complete the class RecentCounter. ping(t) records a request at time t (in milliseconds) and returns how many requests happened in the inclusive range [t - 3000, t], counting this one. Every call uses a larger t than the call before. Use a collections.deque and drop old times from the front, so each ping is O(1) amortised. Example: pings at 1, 100, 3001, 3002 return 1, 2, 3, 3.

Task 2 · Code · Python

Complete the class CircularQueue(capacity) (capacity >= 1). Store the items in a list of length capacity made once in __init__, and never grow it; track a head index and a count and wrap indexes with % capacity. Methods: enqueue(x) adds at the back and returns True, or returns False if the queue is full; dequeue() removes and returns the front item, or returns None if empty; front() returns the front item without removing it, or None; size(), is_empty() and is_full(). Every method O(1).

Task 3 · Code · Javascript

Write a JavaScript class Queue with enqueue(x), dequeue() and size(). dequeue() removes and returns the oldest item, or returns undefined when the queue is empty. Do NOT use shift() (it is O(n)); keep the items in an array plus a head index that moves forward, so every operation is O(1) amortised. Items can be any value, including 0.

Quick quiz

Question 1

1.Items 1, 2, 3 are enqueued in that order. What does the first dequeue return?

Question 2

2.Why is list.pop(0) a poor dequeue for a long Python list?

Question 3

3.Which deque operation is NOT O(1)?

Question 4

4.A circular queue has capacity 4, head = 3 and count = 2. Which slot does the next enqueue use?

Question 5

5.In JavaScript, what is the usual way to avoid the O(n) cost of shift() in a queue?

Read more (free)

Open a question to see the answer and why. The first ones are this topic's own quiz, so try the quiz above before you peek.

Items 1, 2, 3 are enqueued in that order. What does the first dequeue return?

Answer: 1

First in, first out: 1 joined first, so it leaves first.

Why is list.pop(0) a poor dequeue for a long Python list?

Answer: Every remaining item shifts one place left, so it is O(n)

Lists store items side by side; removing the front means moving all the others.

Which deque operation is NOT O(1)?

Answer: q[len(q) // 2]

A deque is a linked structure of blocks: both ends are O(1), the middle is O(n).

A circular queue has capacity 4, head = 3 and count = 2. Which slot does the next enqueue use?

Answer: 1

(3 + 2) % 4 = 1. The items sit in slots 3 and 0; the next one goes in slot 1.

In JavaScript, what is the usual way to avoid the O(n) cost of shift() in a queue?

Answer: Keep a head index that moves forward, or use a linked list

Moving an index is O(1); the used part of the array can be dropped now and then.

Without a count, why is head == tail ambiguous in a circular buffer?Related

Answer: It can mean either empty or full

Use a count or keep one slot empty.

From Queues and circular buffers

Which is the best tool for a large FIFO queue in Python?Related

Answer: collections.deque with append and popleft

deque adds and removes at both ends in O(1). list.pop(0) shifts every item, which is O(n).

From Stacks and queues

Which container allows fast push and pop at BOTH ends and indexing with []?Related

Answer: std::deque

deque is a double-ended queue with random access.

From set, stack, queue, deque and priority_queue

Which removes and returns the head of a Queue, or null if it is empty?Related

Answer: poll()

poll returns null when empty; remove throws.

From Queues, stacks and priority queues

Pass the quiz (60% or more) or mark this topic as done when you are ready.

Your progress is saved on this device only.

Next steps

  1. 3 more topics in “Stacks, queues and linked lists” before its exam: Monotonic stacks and queues, Linked lists, Fast and slow pointers and merging lists.
  2. Module 4 exam: Stacks, queues and linked lists · 14 questions · 25 min, without help. 5 topics of this module still to finish first.
  3. Then the next module, Recursion and backtracking. 61 topics left in the course before the final exam.

← Previous: Stacks