Beginner: foundations · Stacks, queues and linked lists · topic 17 of 61 · 35 min
Queues and deques
Explanation
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).
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.
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)| Operation | list | deque |
|---|---|---|
| Add at the back | append: O(1) amortised | append: O(1) |
| Remove from the front | pop(0): O(n) | popleft(): O(1) |
| Add at the front | insert(0, x): O(n) | appendleft(x): O(1) |
| Read an item in the middle | a[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).
| Step | Slots | head | count |
|---|---|---|---|
| start | [ _, _, _ ] | 0 | 0 |
| enqueue 1, 2, 3 | [ 1, 2, 3 ] | 0 | 3 (full) |
| dequeue → 1 | [ _, 2, 3 ] | 1 | 2 |
| enqueue 4 at (1 + 2) % 3 = 0 | [ 4, 2, 3 ] | 1 | 3 (full) |
| dequeue → 2 | [ 4, _, 3 ] | 2 | 2 |
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
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).
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]])Practice (0/3 done)
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.
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).
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
Read more (free)
Related help
Before this, learn
This topic builds on these. If something here feels new, open them first.
Related topics
- Monotonic stacks and queuesStacks, queues and linked lists · same module
- Linked listsStacks, queues and linked lists · same module
- Queues and circular buffersC Programming (from zero to systems) · Data structures in C
- Stacks and queuesPython Programming · Algorithms, data structures and testing
- set, stack, queue, deque and priority_queueC++ Programming (modern C++ and DSA) · The Standard Template Library (STL)
- Queues, stacks and priority queuesJava Programming (Core to Spring Boot) · The collections framework
Questions people ask about this
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.
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.
Which removes and returns the head of a Queue, or null if it is empty?Related
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
- 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.
- Module 4 exam: Stacks, queues and linked lists · 14 questions · 25 min, without help. 5 topics of this module still to finish first.
- Then the next module, Recursion and backtracking. 61 topics left in the course before the final exam.