Queue and Deque
A queue serves in arrival order. New elements join at the back, and only the element at the front can leave, so the element removed is always the one that has waited longest. That rule is called FIFO, first in, first out. A deque loosens it and allows insertion and removal at both ends.
Intuition: a queue at a shop, and what it guarantees
A stack serves the most recent first. A queue serves the earliest first.
The stack in the previous chapter always serves the most recent item first. Now imagine a print shop with one printer. If it always printed the newest file first, then as long as new jobs keep arriving, the oldest file would never be printed. Computer science calls that starvation. Print jobs, food orders, support tickets, and message systems do not want the most recent first. They want the earliest first.
The structure built for that order is the queue. Think of a queue at a shop: new customers join at the back (rear), and the clerk only serves the person at the front. Nobody can move past anyone else, so the person served is always the one who has waited longest. That is the whole contract, and it has a name: FIFO (First In, First Out). Three rules follow from it:
enqueue adds at the back. dequeue removes at the front. That single split is the difference from a stack, where both operations act on the same end.
The order of arrival is the order of service. No element can be overtaken, so nothing waits forever.
A queue offers no operation to read or change an element in the middle. As with the stack, a smaller set of abilities is what keeps every operation O(1).
Where queues appear
Task scheduling in an operating system, printer job queues, and message systems such as Kafka and RabbitMQ, where requests wait in a queue and the backend consumes them at its own rate. Queues also drive breadth-first search (BFS) on trees (chapter 7) and graphs (chapter 12): the queue is what makes BFS finish one level before starting the next, and that ordering is the reason BFS finds a path with the fewest edges in an unweighted graph. BFS itself comes later; here you only need the queue.
In memory: from a wasteful array to a circular queue
Both ends of a queue must be O(1). A plain array cannot do that, and the modulo operator is what fixes it.
Storing a queue in a plain array causes a problem straight away. Adding at the back is what arrays are good at: append is O(1). Removing at the front is the operation the array chapter warned about: every remaining element moves one position left, so a dequeue costs O(n), and it costs more as the queue gets longer. You could refuse to move anything, but then the slots freed at the front are never used again. Three designs, side by side:
Design 3 is the circular queue, also called a ring buffer in production code. The array is still a straight line in memory, but every index calculation ends with % capacity, which makes the line behave logically like a circle. front consumes from one side, rear wraps around and refills from the other, and as long as the queue is not full there is always a free slot. RingLab in §03 lets you turn the circle yourself.
Keep two pointers, head and tail. Dequeue unlinks the head node (O(1)); enqueue links a new node after tail (O(1)). Why not the other way around? Removing the last node of a singly linked list means finding its predecessor, which is O(n), while removing at the head and inserting at the tail need no traversal. The same rule as always: use the end that costs nothing. A linked queue never resizes and never fills up. The price is one pointer per element and poor cache behavior.
A deque (double-ended queue, pronounced “deck”) removes one more restriction: you can insert and remove at both ends, and all four operations are O(1). Use one end only and it behaves as a stack; add at one end and remove at the other and it behaves as a queue. The monotonic deque in §06 needs both ends at the same time, which is why it must be a deque.
Ring buffers in production
Packet buffers in network drivers, sample buffers in audio devices, fixed-size log buffers, and the core of the LMAX Disruptor trading framework are all ring buffers. The reason is the same every time: a fixed-length array plus two wrapping indices means no allocation, no element ever moves, and the memory touched stays close together. A producer and a consumer each follow their own index.
Core operations: all O(1), and how the indices wrap
A complexity table, RingLab, and the answer to: does front == rear mean full or empty?
| Operation | Meaning | Cost | Why |
|---|---|---|---|
| enqueue(x) | Add x at the back | O(1) | Write into the slot rear points at (or link after tail), then move the index one step |
| dequeue() | Remove the front element | O(1) | A circular queue only moves front; a linked queue unlinks the head node. Nothing is copied |
| peek() | Read the front without removing | O(1) | Read the slot front points at, or the head node |
| isEmpty() / size() | Empty test and count | O(1) | Compare the two indices, or read the counter |
| Deque, both ends | Insert and remove at either end | O(1) | In a circular array both indices can move forward and backward; in a doubly linked list both ends have a handle |
| Read or search the middle | Not provided | O(n) | A queue does not offer it. Use an array when you need random access |
Turn the ring a few times and watch three things. First, a dequeue moves no element at all. Second, what rear does when it reaches the last slot. Third, why full and empty end up looking identical, and the two ways to tell them apart:
Full or empty? The classic circular queue problem
Empty means front has caught up with rear. Full means rear has travelled a full circle and caught up with front. In both cases front == rear, so the indices alone cannot tell you which one it is. The design has to resolve this, and there are two standard answers. Scheme A keeps one slot permanently empty: allocate k + 1 slots and treat (rear + 1) % cap == front as full. It needs no extra variable and costs one slot. Scheme B keeps a size counter: empty is size == 0, full is size == cap. Every slot is usable, and every enqueue and dequeue updates the counter. Both are correct; the implementation in §04 uses scheme A, and RingLab lets you switch between them.
Build one: a circular queue (this is LC 622)
Everything RingLab does, written as code you can submit.
The class below is exactly the solution to LeetCode 622, Design Circular Queue: a fixed-length array, a front and a rear index, and the modulo operator to wrap around. It uses scheme A, keeping one slot empty, which is why the constructor allocates k + 1 slots. Every line matches an action in RingLab:
-1 % 8 == 7, because the result takes the sign of the divisor. There is no need to add the length first the way Java and JavaScript do. If you write in more than one language, (i - 1 + n) % n is safe everywhere.The linked queue is worth writing once as well. It is the form a queue usually takes inside the BFS code of chapter 7, and it hides one boundary case that interviews ask about: resetting tail.
Why out at the head and in at the tail?
In a singly linked list, removing at the head is O(1), inserting at the tail is O(1) when you keep a tail pointer, and removing at the tail is O(n) because you have to find the predecessor. So dequeue must be at the head and enqueue must be at the tail. Turn the two around and one of them becomes O(n). It is the same reasoning as the array stack keeping its top at the end and the linked stack keeping its top at the head: use the end that costs nothing.
Three languages: JavaScript has no queue type
Java and Python both ship one. JavaScript does not, so you build it.
The queue is the structure where the three standard libraries differ the most. Java and Python both provide a deque you can use directly. JavaScript provides nothing: Array.prototype.shift() looks like a dequeue, but it removes the first element and shifts the rest, which is O(n) in general. One language at a time:
q[i] in the middle becomes O(n). list.pop(0) is the most common performance mistake in Python interview code.| Operation | Java (ArrayDeque) | Python (deque) | JavaScript (head index) | Complexity |
|---|---|---|---|---|
| Enqueue (back) | q.offer(x) | q.append(x) | q.push(x) | O(1) amortized |
| Dequeue (front) | q.poll() | q.popleft() | q[head++] | O(1) |
| Look at the front | q.peek() | q[0] | q[head] | O(1) |
| Insert at the front | q.offerFirst(x) | q.appendleft(x) | linked queue or two stacks | O(1) |
| Remove from the back | q.pollLast() | q.pop() | q.pop() | O(1) |
| Avoid | LinkedList (poor cache behavior) | list.pop(0) | arr.shift() | O(n) |
Patterns, and the monotonic deque
★ Interview coreThe amortized analysis of a queue built from two stacks, and the standard solution for sliding window extremes.
Two LIFO structures make one FIFO structure, because reversing twice restores the original order. It is the standard example for amortized analysis. LC 232, walkthrough A.
The maximum or minimum of a sliding window, available in O(1) at any moment. Two rules: drop weaker candidates at the back, drop expired indices at the front. LC 239, 1438, 862; walkthrough B.
Palindrome checking (compare the two ends, then move inward), 0-1 BFS (an edge of weight 0 goes to the front, an edge of weight 1 goes to the back; chapter 12), and work-stealing schedulers (a thread uses its own end as a stack while other threads take tasks from the far end).
A monotonic deque is a deque whose contents stay in sorted order; the order is kept by removing elements before each insertion. Take the maximum of a sliding window as the goal. The deque holds indices, not values, and the values at those indices decrease from front to back. Two rules keep that true, one at each end:
Before index i enters, remove from the back every index whose value is not greater than nums[i]. Each of them is smaller than the new element and also leaves the window earlier, so none of them can ever be a maximum again. Indices with an equal value are removed too, so the values in the deque strictly decrease.
When the front index falls outside the window, remove it from the front. Only the front can expire, because it is the oldest index in the deque, so one check per step is enough.
With both rules maintained, the front is always the index of the maximum of the current window, and reading it is O(1). One end drops weaker candidates and the other drops expired indices, so the structure has to be a deque.
The code has a while loop inside a for loop, which makes many learners assume O(n²). It is not. Each index enters the deque exactly once and leaves at most once, from either end, so the total number of deque operations over the whole array is at most 2n. The running time is O(n), and the space is O(k) because the deque never holds more indices than the window contains.
Monotonic stack and monotonic deque, side by side
Both reach O(n) the same way: an element that can never be the answer is discarded early. The difference is which end elements leave from. A monotonic stack answers questions such as “next greater element”, and an element’s answer is settled at the moment it is popped. A monotonic deque answers questions about a window, and it adds a second rule: the index at the front expires once the window moves past it. One end is enough for the stack; the window needs both.
LC 232 · Implement Queue using Stacks
EASYThe problem: implement push, pop, peek, and empty for a queue, using only stacks. The idea: a stack reverses the order, so reverse it a second time. The naive version: on every push, use the second stack to place the new element at the bottom, which makes push O(n). The better version: do not move anything until you have to. The in stack only accepts pushes, the out stack only serves pops, and in is emptied into out only when out is empty:
Why pop is O(1) amortized
A single pop can be O(n), when it triggers the transfer. Count elements instead of operations. In its whole life an element is moved at most 4 times: into in, out of in, into out, out of out. Each of those happens at most once, because the transfer never runs while out is not empty, so no element is ever transferred twice. n operations therefore cost at most 4n moves, which averages to O(1) per operation. Array growth (1 + 2 + 4 + … < 2n) and the monotonic stack (each element pushed once and popped once, at most 2n) are counted the same way: amortized analysis spreads the cost of a rare expensive step over all the operations.
Complexity and follow-up questions
push is O(1); pop and peek are O(1) amortized; space is O(n). First follow-up: why must the transfer be skipped while out is not empty? Because the elements moved across would land on top of the older ones, and the order would immediately be wrong. Second follow-up: what about a stack built from queues? That is LC 225, in the problem set: after each push, move the n − 1 earlier elements to the back, which makes push O(n) and pop O(1). The two problems mirror each other.
LC 239 · Sliding Window Maximum
HARDMonotonic dequeThe problem: a window of length k slides from left to right; report the maximum at every position. Brute force: scan k elements per window, O(nk), which is too slow at n = 10⁵. Why it can be improved: two neighboring windows share k − 1 elements, so brute force scans almost the same stretch again and again. It also throws information away: once an element has a larger element to its right, it can never be the maximum of any later window. Those elements do not need to be kept. Keep only the candidates that are still possible, in decreasing order, in a deque. That is a monotonic deque:
Complexity and follow-up questions
Time O(n): each index enters the deque once and leaves at most once, so there are at most 2n deque operations in total, even though the code has a while loop inside a for loop. Space O(k). First follow-up: the minimum of the window? Reverse every comparison and keep the values increasing. Second follow-up: the maximum and the minimum at the same time? Run two monotonic deques over the same window (LC 1438, in the problem set). Third follow-up: why store indices instead of values? Because the expiry test needs a position (front ≤ i − k). An index gives you the value, and a value does not give you the index — the same reason a monotonic stack stores indices.
Problem set: 8 queue problems
SelectedSimulation, then design, then monotonic deques, easy to hard. Think for 30 seconds before you open the hint.
Quiz
✎ QuizAll 7 correct turns this chapter green.
What does FIFO (First In, First Out) mean for a queue?
A circular queue runs rear = (rear + 1) % capacity on enqueue. What is the modulo for?
In a circular queue, front == rear. Is the queue full or empty?
What is the cost of inserting and removing at both ends of a deque?
You need a queue in JavaScript. Which of these keep dequeue at O(1)? (select all)
In a monotonic deque (take sliding window maximum, LC 239), what does the deque hold?
In the two-stack queue (LC 232), each element is moved at most 4 times in its whole life (into in, out of in, into out, out of out). So the amortized cost of one dequeue is O(___)?
- A queue splits the two ends: enqueue at the back, dequeue at the front. The element removed is always the one that has waited longest, which is what FIFO means. A deque opens both ends and can act as a stack or as a queue.
- A plain array queue forces a bad choice: shift on every dequeue (O(n)), or leave the freed front slots unused forever. The circular queue removes both problems by taking every index
% cap. front == rear then means either full or empty, so the design resolves it by keeping one slot empty or by keeping a size counter. - What to use:
ArrayDequein Java (Queueis an interface;LinkedListimplements it too but is slower, and ArrayDeque rejectsnull);collections.dequein Python (notlist.pop(0), andqueue.Queueis a separate class for threads). JavaScript has no queue type andshift()is O(n) in general: use a head index, two stacks, or a linked queue. - A queue from two stacks: reversing twice restores the arrival order, and each element is moved at most 4 times in its whole life, so pop is O(1) amortized — the same accounting as array growth and the monotonic stack.
- A monotonic deque holds indices whose values decrease from front to back. Remove from the back every index whose value is not greater than the new element, and remove the front index once it has left the window. The front is then always the maximum of the current window, and the whole scan is O(n), because each index enters once and leaves at most once.