Data Structures
Queue
First in, first out — the line at the coffee shop
A queue is a linear data structure where the first item added is the first one removed (FIFO). Enqueue at the rear, dequeue from the front, both in O(1). Queues power breadth-first search, message buffers, task schedulers, and the print spooler — anywhere ordering must be preserved.
- Enqueue (rear)O(1)
- Dequeue (front)O(1)
- Peek (front)O(1)
- Search by valueO(n)
- SpaceO(n)
- OrderingFIFO
Interactive visualization
Press play, or step through manually. The visualization is yours to drive — try it before reading on.
Watch the 60-second explainer
A condensed visual walkthrough — narrated, captioned, under a minute.
How a queue works
A queue tracks two ends: the front (where elements leave) and the rear (where new elements arrive). Operations:
- Enqueue — append to the rear.
- Dequeue — remove from the front and return it.
- Peek — read the front element without removing it.
The discipline of "always touch the same end for inserts, always the other end for removes" is what gives queues their FIFO property. The first thing in is the first thing out.
Three ways to implement a queue
| Linked list | Circular buffer (array) | Two stacks | |
|---|---|---|---|
| Enqueue | O(1) | O(1) | O(1) |
| Dequeue | O(1) | O(1) | O(1) amortized |
| Memory layout | Scattered | Contiguous | Contiguous (×2) |
| Cache locality | Poor | Excellent | Excellent |
| Capacity | Unbounded | Fixed (without resize) | Unbounded |
| Best for | Variable size, simple impl | Fixed buffer (audio, packets) | Functional langs, interview Qs |
Naive array queues — where you shift every element on dequeue — are O(n) per dequeue and the pattern in code reviews to flag. Don't use them. Either use a circular buffer (best for fixed-size) or a linked list (best for variable-size).
When to use a queue
- Breadth-first search. Any algorithm that processes "all level-N items before any level-(N+1) item" needs a queue. BFS, level-order tree traversal, shortest path on unweighted graphs.
- Message buffers. Producer-consumer patterns: producer enqueues messages as they arrive, consumer dequeues them in order. Network packets, log entries, OS interrupts.
- Task scheduling. Round-robin schedulers, print queues, request handlers. Whichever job arrived first runs first.
- Rate limiting and throttling. Sliding-window rate limiters use queues to track request timestamps.
- Streaming and pipelines. Each stage has an input queue; the previous stage enqueues, this stage dequeues. Backpressure is just "the queue filled up."
Pseudo-code (linked-list queue)
structure Queue:
head = null
tail = null
enqueue(x):
node = Node(x, next=null)
if tail == null:
head = tail = node
else:
tail.next = node
tail = node
dequeue():
if head == null: error("empty")
x = head.value
head = head.next
if head == null: tail = null
return x
peek():
if head == null: error("empty")
return head.value
JavaScript implementation (circular buffer)
class CircularQueue {
constructor(capacity) {
this.buf = new Array(capacity);
this.cap = capacity;
this.head = 0;
this.tail = 0;
this.size = 0;
}
enqueue(x) {
if (this.size === this.cap) throw new Error('overflow');
this.buf[this.tail] = x;
this.tail = (this.tail + 1) % this.cap;
this.size++;
}
dequeue() {
if (this.size === 0) throw new Error('empty');
const x = this.buf[this.head];
this.buf[this.head] = undefined;
this.head = (this.head + 1) % this.cap;
this.size--;
return x;
}
peek() { return this.size === 0 ? undefined : this.buf[this.head]; }
}
Python implementation
from collections import deque
# For 99% of Python code, just use deque — already a queue:
q = deque()
q.append(1) # enqueue
q.append(2)
front = q.popleft() # dequeue, returns 1
# Hand-rolled linked-list queue:
class Node:
__slots__ = ('value', 'next')
def __init__(self, v): self.value = v; self.next = None
class Queue:
def __init__(self):
self.head = self.tail = None
self.size = 0
def enqueue(self, x):
node = Node(x)
if self.tail: self.tail.next = node
else: self.head = node
self.tail = node
self.size += 1
def dequeue(self):
if not self.head: raise IndexError('empty')
x = self.head.value
self.head = self.head.next
if not self.head: self.tail = None
self.size -= 1
return x
Famous queue problems
Queue using two stacks
class QueueFromStacks {
constructor() { this.in = []; this.out = []; }
enqueue(x) { this.in.push(x); }
dequeue() {
if (this.out.length === 0) {
while (this.in.length) this.out.push(this.in.pop());
}
if (this.out.length === 0) throw new Error('empty');
return this.out.pop();
}
}
Each element is moved at most twice (in → out), so amortized O(1) per dequeue. A single dequeue can be O(n) if it triggers a transfer, but across n operations the total work is O(n).
Breadth-first search skeleton
function bfs(graph, start) {
const visited = new Set([start]);
const queue = [start];
while (queue.length) {
const node = queue.shift(); // dequeue
for (const neighbor of graph[node]) {
if (!visited.has(neighbor)) {
visited.add(neighbor);
queue.push(neighbor); // enqueue
}
}
}
}
Note: Array.shift() is O(n) in JavaScript. For real production BFS on large graphs, use a deque or maintain a head index manually instead of shifting. The pattern is right; the implementation needs care.
Common bugs and edge cases
- Forgetting to update tail when the queue empties. If you dequeue the last element and don't reset
tail = null, subsequent enqueues link onto a stale tail and the head/tail pointers diverge. - Off-by-one in circular buffer modulo. Easy to confuse "size == cap" (full) with "head == tail" (empty or full?). Track size explicitly OR use head/tail with a one-slot-empty convention.
- O(n) dequeue from a naive array. If you implement dequeue as
arr.shift(), every dequeue shifts every remaining element. The whole queue becomes O(n²) over n operations. Use a circular buffer or maintain a head index. - Confusing queue and deque. A queue is FIFO; a deque is double-ended. If you find yourself wanting to insert at the front or remove from the back, you actually want a deque.
- Priority queue ≠ queue. Despite the name, a priority queue is a heap. Don't reach for it when you need FIFO — its ordering is by priority, not insertion time.
Frequently asked questions
Why is dequeue O(1)?
The queue tracks a head pointer. Dequeue reads the head, advances the pointer, done. The dequeued slot stays allocated (in array-backed circular queues) or gets unlinked from the head (in linked-list queues). Either way, no shifting, no scanning.
What's the difference between a queue and a stack?
Direction. A stack adds and removes from the same end (top); a queue adds at one end and removes from the other. Stacks reverse insertion order on output; queues preserve it. Both are O(1) on their main operations.
What's a circular queue?
An array-backed queue where the head and tail wrap around modulo the array size. Standard array queues waste space — once you dequeue from the front, those slots are unused unless you shift. A circular queue uses head and tail indices that wrap with `% capacity`, so the storage is reused. Used for fixed-size buffers, network packet queues, and ring buffers in audio/video pipelines.
How do you implement a queue using two stacks?
One stack for enqueue (push), one for dequeue (pop). When dequeue is empty, transfer everything from enqueue to dequeue — pushing one stack into another reverses the order, so what comes off dequeue is FIFO. Each element is moved at most twice, giving amortized O(1) per operation. Classic interview question that demonstrates amortized analysis.
What's a deque?
Double-ended queue. Insert and remove at both ends in O(1). Generalizes both stack and queue. Python's `collections.deque` and Java's `ArrayDeque` are deques implemented as circular buffers or unrolled linked lists. If you need both stack and queue behavior, reach for a deque.
How does breadth-first search use a queue?
BFS explores a graph level by level. Push the start node onto the queue. Loop: dequeue a node, mark visited, enqueue all unvisited neighbors. The FIFO ordering ensures level-N nodes are processed before any level-(N+1) node. Swap the queue for a stack and you get DFS — same code, different traversal order.
What's a priority queue and is it really a queue?
A priority queue dequeues the highest-priority item, not the oldest. The "queue" in the name is misleading — a priority queue is usually implemented as a binary heap, with O(log n) insert and O(log n) extract-min. It shares the enqueue/dequeue interface with a queue but the underlying ordering rule is different.