First in, first out. The data structure behind every line you've ever stood in โ from printer jobs to breadth-first search.
โฌก BeginnerA queue is a collection where elements are processed in the order they arrived. The first item added is the first one removed โ this is called FIFO (First In, First Out).
Think about a line at a coffee shop. The person who arrived first gets served first. New people join at the back. Nobody cuts. That's a queue.
Queues in computing trace back to early work on operating systems in the late 1950s and early 1960s. As computers evolved from single-task machines to multi-program systems (where multiple jobs needed to share the CPU), engineers needed a fair way to order tasks. The queue was the natural answer โ processes that arrived first got executed first.
The earliest formal treatment came from queueing theory, a branch of mathematics pioneered by Agner Krarup Erlang at the Copenhagen Telephone Exchange around 1909. Erlang was trying to figure out how many telephone lines were needed to handle call volume. His models โ which used queues of incoming calls โ became the foundation for modern network design, traffic engineering, and any system where requests line up for service.
Regular Queue โ strict FIFO. Items enter at the rear (back), leave from the front. That's it.
Priority Queue โ each item has a priority (a numeric value that determines its importance). Instead of serving in arrival order, the highest-priority item gets served first. Hospital emergency rooms work this way: a broken arm waits while a heart attack goes straight in, regardless of who showed up first. Under the hood, priority queues usually use a heap (a tree-shaped data structure stored in an array), not an actual queue.
Deque (pronounced "deck") โ short for double-ended queue. You can add or remove items from both the front and the back. It's a queue and a stack at the same time. Useful when you need flexibility, like in a sliding window algorithm where elements can expire from the front and new ones arrive at the back.
The simplest implementation uses an array with two pointers: front (the index of the next element to dequeue) and rear (the index where the next element will be enqueued).
When you enqueue (add an item), it goes at the rear position, and rear moves forward. When you dequeue (remove an item), you take from the front position, and front moves forward.
The problem: both pointers only move right. After enough operations, you'll waste the left side of the array even though there's space. The fix is a circular queue (also called a ring buffer) โ when a pointer reaches the end of the array, it wraps around to position 0 using the modulo operator: rear = (rear + 1) % capacity. Think of it like a clock โ after 12, you go back to 1.
You can use a regular array as a queue by using shift() in JavaScript or pop(0) in Python. But both are O(n) because every remaining element has to scoot left to fill the gap. A proper queue (using a circular buffer or linked list) gives you O(1) for both enqueue and dequeue.
A priority queue doesn't maintain FIFO order โ it maintains priority order. The standard implementation uses a binary min-heap: a complete binary tree (every level is fully filled except possibly the last, which fills left to right) where every parent node's value is smaller than (or equal to) its children's values.
Insert: add the new element at the bottom-right position (end of the array), then "bubble up" by swapping with its parent until the heap property holds.
Remove: take the root (the minimum), move the last element to the root position, then "sift down" by swapping with the smaller child until the heap property is restored.
Both operations are O(log n) because the tree height is log n.
A deque supports four operations: add/remove from front, add/remove from back. A doubly linked list (where each node has pointers to both its next and previous neighbors) makes this trivial โ just update the head or tail pointer. Python's collections.deque uses a doubly linked list of fixed-size blocks, giving you the cache-friendly performance of arrays with the flexibility of linked lists.
The circular queue is worth understanding deeply because it shows up in systems programming everywhere โ network buffers, audio processing, producer-consumer patterns.
Key implementation details:
(index + 1) % capacity to wrap around(rear + 1) % capacity == front โ this wastes one slot but avoids ambiguity with the empty casefront == rearsize variable and compare against capacity, which uses all slotsTry it yourself. Enqueue items, dequeue them, and toggle priority mode to see how ordering changes.
| Operation | Time | Space | Notes |
|---|---|---|---|
| Enqueue | O(1) | O(1) | Add to rear |
| Dequeue | O(1) | O(1) | Remove from front |
| Peek / Front | O(1) | O(1) | Look without removing |
| Search | O(n) | O(1) | Must check each element |
| isEmpty | O(1) | O(1) | Compare front/rear pointers |
| Operation | Time | Space | Notes |
|---|---|---|---|
| Insert | O(log n) | O(1) | Bubble up after adding |
| Extract Min/Max | O(log n) | O(1) | Bubble down after removing root |
| Peek | O(1) | O(1) | Root is always the min/max |
| Build Heap | O(n) | O(1) | Heapify from bottom โ not O(n log n)! |
| Search | O(n) | O(1) | Heap isn't ordered for search |
| Operation | Time | Space | Notes |
|---|---|---|---|
| Push Front / Back | O(1) | O(1) | Amortized for array-based |
| Pop Front / Back | O(1) | O(1) | Amortized for array-based |
| Peek Front / Back | O(1) | O(1) | Direct access to both ends |
| Search | O(n) | O(1) | Linear scan required |
from collections import deque
class Queue:
def __init__(self):
# deque gives us O(1) popleft โ a plain list would be O(n)
self._data = deque()
def enqueue(self, val):
self._data.append(val)
def dequeue(self):
if not self._data:
raise IndexError("dequeue from empty queue")
return self._data.popleft()
def peek(self):
if not self._data:
raise IndexError("peek from empty queue")
return self._data[0]
def is_empty(self):
return len(self._data) == 0
def __len__(self):
return len(self._data)
# Usage
q = Queue()
q.enqueue(10)
q.enqueue(20)
q.enqueue(30)
print(q.dequeue()) # 10 โ first in, first out
print(q.peek()) # 20 โ next in line, but stays
class Queue {
constructor() {
// Use a map with numeric keys instead of an array
// so dequeue doesn't shift every element (O(n) with arrays)
this._data = {};
this._head = 0;
this._tail = 0;
}
enqueue(val) {
this._data[this._tail] = val;
this._tail++;
}
dequeue() {
if (this.isEmpty()) throw new Error("Queue is empty");
const val = this._data[this._head];
delete this._data[this._head];
this._head++;
return val;
}
peek() {
if (this.isEmpty()) throw new Error("Queue is empty");
return this._data[this._head];
}
isEmpty() {
return this._tail === this._head;
}
get size() {
return this._tail - this._head;
}
}
// Usage
const q = new Queue();
q.enqueue(10);
q.enqueue(20);
q.enqueue(30);
console.log(q.dequeue()); // 10
console.log(q.peek()); // 20
class CircularQueue:
"""Fixed-size circular queue (ring buffer).
Used in: network packet buffers, audio processing,
producer-consumer patterns, kernel I/O buffers.
Wraps around using modulo arithmetic โ when the pointer
reaches the end of the array, it jumps back to index 0.
"""
def __init__(self, capacity):
self._data = [None] * capacity
self._capacity = capacity
self._front = 0
self._rear = 0
self._size = 0
def enqueue(self, val):
if self._size == self._capacity:
raise OverflowError("queue is full")
self._data[self._rear] = val
self._rear = (self._rear + 1) % self._capacity # wrap around
self._size += 1
def dequeue(self):
if self._size == 0:
raise IndexError("queue is empty")
val = self._data[self._front]
self._data[self._front] = None # help GC
self._front = (self._front + 1) % self._capacity # wrap around
self._size -= 1
return val
def peek(self):
if self._size == 0:
raise IndexError("queue is empty")
return self._data[self._front]
def is_full(self):
return self._size == self._capacity
def is_empty(self):
return self._size == 0
import heapq
# Python's heapq is a min-heap โ smallest value comes out first
pq = []
heapq.heappush(pq, (3, "low priority"))
heapq.heappush(pq, (1, "high priority"))
heapq.heappush(pq, (2, "medium priority"))
# Items come out in priority order, not insertion order
while pq:
priority, task = heapq.heappop(pq)
print(f"Priority {priority}: {task}")
# Output:
# Priority 1: high priority
# Priority 2: medium priority
# Priority 3: low priority
# For a max-heap, negate the priority
heapq.heappush(pq, (-5, "important"))
heapq.heappush(pq, (-1, "trivial"))
priority, task = heapq.heappop(pq)
print(-priority, task) # 5, "important"
class MinHeap {
constructor() {
this._heap = [];
}
push(val) {
this._heap.push(val);
this._bubbleUp(this._heap.length - 1);
}
pop() {
if (!this._heap.length) throw new Error("Heap is empty");
const min = this._heap[0];
const last = this._heap.pop();
if (this._heap.length) {
this._heap[0] = last;
this._bubbleDown(0);
}
return min;
}
peek() {
return this._heap[0];
}
_bubbleUp(i) {
// Keep swapping with parent until heap property holds
while (i > 0) {
const parent = Math.floor((i - 1) / 2);
if (this._heap[i] >= this._heap[parent]) break;
[this._heap[i], this._heap[parent]] = [this._heap[parent], this._heap[i]];
i = parent;
}
}
_bubbleDown(i) {
const n = this._heap.length;
while (true) {
let smallest = i;
const left = 2 * i + 1;
const right = 2 * i + 2;
// Find the smallest among node and its children
if (left < n && this._heap[left] < this._heap[smallest]) smallest = left;
if (right < n && this._heap[right] < this._heap[smallest]) smallest = right;
if (smallest === i) break;
[this._heap[i], this._heap[smallest]] = [this._heap[smallest], this._heap[i]];
i = smallest;
}
}
get size() { return this._heap.length; }
}
list.pop(0) in Python as a queue. It's O(n) because every remaining element shifts left. Use collections.deque which gives O(1) popleft(). This is probably the most common Python performance mistake.Array.shift() in JavaScript. Same problem โ O(n) per call. Use an object with head/tail indices, or a linked list. For small queues (under ~1000 elements) it doesn't matter, but for BFS on a large graph it's devastating.heapq is a min-heap. For max-heap behavior, negate all values: push -x, pop and negate back. Or wrap items in a tuple: (-priority, item).while queue: not assume the queue has elements.Here's a simplified version of how OS task schedulers and job queues work. Tasks arrive with priorities and get processed in priority order, not arrival order.
import heapq
import time
class TaskScheduler:
"""Priority-based task scheduler.
Tasks with lower priority numbers run first.
Ties are broken by arrival order (FIFO within same priority).
This is the basic pattern behind: OS process schedulers,
Celery/RQ job queues, hospital triage systems, Dijkstra's algorithm.
"""
def __init__(self):
self._queue = []
self._counter = 0 # Tiebreaker for same-priority tasks
def add_task(self, name, priority=5):
# Tuple comparison: first by priority, then by arrival order
# The counter ensures FIFO ordering within the same priority
heapq.heappush(self._queue, (priority, self._counter, name))
self._counter += 1
print(f" + Queued: '{name}' (priority {priority})")
def run_next(self):
if not self._queue:
print(" No tasks in queue")
return None
priority, _, name = heapq.heappop(self._queue)
print(f" โถ Running: '{name}' (priority {priority})")
return name
def run_all(self):
print("Running all tasks in priority order:")
while self._queue:
self.run_next()
# Simulate a server processing requests
scheduler = TaskScheduler()
scheduler.add_task("Send weekly report email", priority=3)
scheduler.add_task("Process payment", priority=1) # urgent!
scheduler.add_task("Generate thumbnails", priority=5)
scheduler.add_task("Send password reset", priority=2)
scheduler.add_task("Clean temp files", priority=5)
print()
scheduler.run_all()
# Output:
# โถ Running: 'Process payment' (priority 1)
# โถ Running: 'Send password reset' (priority 2)
# โถ Running: 'Send weekly report email' (priority 3)
# โถ Running: 'Generate thumbnails' (priority 5)
# โถ Running: 'Clean temp files' (priority 5)
This same pattern powers Kubernetes pod scheduling, airline boarding (priority groups), and network packet QoS (Quality of Service). The priority queue ensures that critical work gets done first, even if lower-priority tasks arrived earlier.
If a problem asks for shortest path in an unweighted graph, level-order traversal, or "minimum steps" โ reach for a queue. BFS explores all nodes at distance d before any at distance d+1, which is exactly what gives you the shortest path guarantee.
Anytime you see "K largest", "K most frequent", "K closest" โ that's a heap. Keep a min-heap of size K. For each new element, push it and pop if the heap exceeds K. You end up with the K largest in O(n log k), which beats sorting at O(n log n) when K is small.
The classic hard problem: find the maximum in every window of size K. A deque that keeps elements in decreasing order gives you O(n) total. When a new element arrives, pop everything smaller from the back (they'll never be the max). Pop from the front if the element has left the window. The front of the deque is always the current max.
Use two stacks. Push into stack A. When you need to dequeue, dump stack A into stack B (reversing the order). Now stack B pops in FIFO order. Only dump when B is empty โ this gives amortized O(1) per operation. Each element gets pushed and popped at most twice total.
When there are multiple starting points (like "distance from nearest 0 in a matrix"), start BFS from ALL sources simultaneously by adding them all to the queue before the first iteration. This avoids running separate BFS from each source. Problems: 01 Matrix (#542), Rotting Oranges (#994).
Process nodes in dependency order. Start by queuing all nodes with zero dependencies (in-degree 0). Process each node, reduce the in-degree of its neighbors, and add newly zero-in-degree nodes to the queue. The processing order is a valid topological sort. If you can't process all nodes, there's a cycle.
Problems: Course Schedule (#207), Course Schedule II (#210), Alien Dictionary.
Ordered roughly from easy to hard. Try to identify the pattern before looking at solutions.
| # | Problem | Difficulty | Pattern |
|---|---|---|---|
| 232 | Implement Queue using Stacks | Easy | Two-stack trick |
| 225 | Implement Stack using Queues | Easy | Queue rotation |
| 933 | Number of Recent Calls | Easy | Sliding window queue |
| 622 | Design Circular Queue | Medium | Circular buffer |
| 347 | Top K Frequent Elements | Medium | Heap / bucket sort |
| 621 | Task Scheduler | Medium | Priority queue + cooldown |
| 994 | Rotting Oranges | Medium | Multi-source BFS |
| 239 | Sliding Window Maximum | Hard | Monotonic deque |
| 23 | Merge K Sorted Lists | Hard | Min-heap merge |