Skip to content

Queues

A queue is a linear data structure that follows the First-In-First-Out (FIFO) principle. Think of it like a line at a store - the first person in line is the first to be served.

Interactive Queue Visualizer

Enqueue and dequeue elements to see FIFO in action. Watch elements enter at the rear and leave from the front.

Data Structure Visualizer

FIFO - First In, First Out

Value
Queue is empty — enqueue an element
Size: 0 / 10

What is a Queue?

A queue supports two primary operations:

  • Enqueue: Add an element to the back
  • Dequeue: Remove and return the front element
Front Rear
↓ ↓
┌────┬────┬────┬────┬────┐
│ 1 │ 3 │ 5 │ 8 │ 2 │
└────┴────┴────┴────┴────┘
↑ ↑
Dequeue Enqueue

Time Complexity

OperationAverageWorst
EnqueueO(1)O(1)
DequeueO(1)O(1)
Front/PeekO(1)O(1)
SearchO(n)O(n)
isEmptyO(1)O(1)

Basic Operations

Queue Implementation

from collections import deque
# Using deque (recommended - O(1) for both ends)
queue = deque()
queue.append(1) # Enqueue
queue.append(2)
front = queue.popleft() # Dequeue: returns 1
# Custom Queue class
class Queue:
def __init__(self):
self.items = deque()
def enqueue(self, item):
"""Add item to rear"""
self.items.append(item)
def dequeue(self):
"""Remove and return front item"""
if self.is_empty():
raise IndexError("Dequeue from empty queue")
return self.items.popleft()
def front(self):
"""Return front item without removing"""
if self.is_empty():
raise IndexError("Front from empty queue")
return self.items[0]
def rear(self):
"""Return rear item without removing"""
if self.is_empty():
raise IndexError("Rear from empty queue")
return self.items[-1]
def is_empty(self):
return len(self.items) == 0
def size(self):
return len(self.items)

Queue Variants

Circular Queue

Fixed-size queue that wraps around, efficient for buffering.

class CircularQueue:
def __init__(self, capacity: int):
self.capacity = capacity
self.items = [None] * capacity
self.head = 0
self.tail = 0
self.size = 0
def enqueue(self, item):
if self.is_full():
raise Exception("Queue is full")
self.items[self.tail] = item
self.tail = (self.tail + 1) % self.capacity
self.size += 1
def dequeue(self):
if self.is_empty():
raise Exception("Queue is empty")
item = self.items[self.head]
self.head = (self.head + 1) % self.capacity
self.size -= 1
return item
def is_empty(self):
return self.size == 0
def is_full(self):
return self.size == self.capacity

Deque (Double-Ended Queue)

Supports add/remove from both ends in O(1).

from collections import deque
dq = deque()
# Add elements
dq.append(1) # Add to right: [1]
dq.append(2) # Add to right: [1, 2]
dq.appendleft(0) # Add to left: [0, 1, 2]
# Remove elements
right = dq.pop() # Remove from right: 2
left = dq.popleft() # Remove from left: 0
# Access without removing
first = dq[0] # Left element
last = dq[-1] # Right element

Priority Queue

Elements are dequeued based on priority, not insertion order.

import heapq
# Min-heap (smallest first)
min_heap = []
heapq.heappush(min_heap, 3)
heapq.heappush(min_heap, 1)
heapq.heappush(min_heap, 2)
smallest = heapq.heappop(min_heap) # Returns 1
# Max-heap (largest first) - negate values
max_heap = []
heapq.heappush(max_heap, -3)
heapq.heappush(max_heap, -1)
heapq.heappush(max_heap, -2)
largest = -heapq.heappop(max_heap) # Returns 3
# With custom objects
from heapq import heappush, heappop
tasks = []
heappush(tasks, (2, "low priority"))
heappush(tasks, (1, "high priority"))
heappush(tasks, (3, "lowest priority"))
_, next_task = heappop(tasks) # "high priority"

Common Patterns

Queue is essential for level-order traversal. BFS explores all neighbors at the current depth before moving to nodes at the next depth level.

Algorithm Visualization:

Graph: A ── B ── D
│ │
C E
F
BFS from A:
Queue: [A] → Process A, add B,C → Result: [A]
Queue: [B, C] → Process B, add D,E → Result: [A, B]
Queue: [C, D, E] → Process C, add F → Result: [A, B, C]
Queue: [D, E, F] → Process D (no neighbors)→ Result: [A, B, C, D]
Queue: [E, F] → Process E (no neighbors)→ Result: [A, B, C, D, E]
Queue: [F] → Process F (no neighbors)→ Result: [A, B, C, D, E, F]
from collections import deque
def bfs(graph, start):
"""
Traverse graph in breadth-first order.
BFS uses a queue (FIFO) to process nodes level by level.
We mark nodes as visited when we ADD them to the queue
(not when we process them) to avoid duplicates.
Time Complexity: O(V + E) - visit each vertex and edge once
Space Complexity: O(V) - queue can hold all vertices
Args:
graph: Adjacency list representation {node: [neighbors]}
start: Starting node for traversal
Returns:
List of nodes in BFS order
"""
visited = set([start]) # Mark visited when ADDING to queue
queue = deque([start])
result = []
while queue:
node = queue.popleft() # FIFO: process oldest first
result.append(node)
for neighbor in graph[node]:
if neighbor not in visited:
visited.add(neighbor) # Mark visited immediately
queue.append(neighbor)
return result
# Example usage
graph = {
'A': ['B', 'C'],
'B': ['D', 'E'],
'C': ['F'],
'D': [], 'E': [], 'F': []
}
print(bfs(graph, 'A')) # ['A', 'B', 'C', 'D', 'E', 'F']

2. Level Order Traversal

Process tree nodes level by level. This is BFS applied to trees, where we group nodes by their depth.

Visualization:

Tree: 1
/ \
2 3
/ \ \
4 5 6
Level 0: [1]
Level 1: [2, 3]
Level 2: [4, 5, 6]
Output: [[1], [2, 3], [4, 5, 6]]
from collections import deque
def level_order(root):
"""
Return nodes grouped by level.
Key insight: Process all nodes at current level before
moving to the next level by tracking level_size.
Time: O(n) - visit each node once
Space: O(n) - queue can hold entire level (worst: n/2 nodes)
"""
if not root:
return []
result = []
queue = deque([root])
while queue:
level_size = len(queue) # Nodes at current level
current_level = []
# Process exactly level_size nodes
for _ in range(level_size):
node = queue.popleft()
current_level.append(node.val)
# Add children for next level
if node.left:
queue.append(node.left)
if node.right:
queue.append(node.right)
result.append(current_level)
return result

3. Sliding Window Maximum

Find the maximum in each sliding window of size k. Uses a monotonic deque to achieve O(n) time.

Key Insight: Maintain a deque where elements are in decreasing order. The front is always the maximum of the current window.

nums = [1, 3, -1, -3, 5, 3, 6, 7], k = 3
Window [1,3,-1]: max=3 deque stores indices of [3,-1]
Window [3,-1,-3]: max=3 deque stores indices of [3,-1,-3]
Window [-1,-3,5]: max=5 deque stores indices of [5]
Window [-3,5,3]: max=5 deque stores indices of [5,3]
Window [5,3,6]: max=6 deque stores indices of [6]
Window [3,6,7]: max=7 deque stores indices of [7]
Output: [3, 3, 5, 5, 6, 7]
from collections import deque
def max_sliding_window(nums: list, k: int) -> list:
"""
Find maximum in each sliding window of size k.
Uses monotonic decreasing deque:
- Front of deque is always the maximum
- Remove elements smaller than current (they can't be max)
- Remove elements outside the window
Time: O(n) - each element added and removed at most once
Space: O(k) - deque holds at most k elements
"""
result = []
dq = deque() # Store INDICES, not values
for i, num in enumerate(nums):
# Remove indices that are outside the current window
while dq and dq[0] < i - k + 1:
dq.popleft()
# Remove indices of elements smaller than current
# They can never be the maximum while current is in window
while dq and nums[dq[-1]] < num:
dq.pop()
dq.append(i)
# Window is fully formed starting at index k-1
if i >= k - 1:
result.append(nums[dq[0]]) # Front is the max
return result
# Example
nums = [1, 3, -1, -3, 5, 3, 6, 7]
print(max_sliding_window(nums, 3)) # [3, 3, 5, 5, 6, 7]

4. Implement Stack using Queues

Classic interview problem demonstrating understanding of both data structures.

from collections import deque
class StackUsingQueues:
"""
Implement stack using a single queue.
Push: O(n) - rotate queue to put new element at front
Pop/Top: O(1)
"""
def __init__(self):
self.q = deque()
def push(self, x: int):
self.q.append(x)
# Rotate queue: move all elements before x to after x
for _ in range(len(self.q) - 1):
self.q.append(self.q.popleft())
def pop(self) -> int:
return self.q.popleft()
def top(self) -> int:
return self.q[0]
def empty(self) -> bool:
return len(self.q) == 0

5. Rotting Oranges (Multi-source BFS)

A classic BFS problem where we start from multiple sources simultaneously.

from collections import deque
def oranges_rotting(grid):
"""
Find minimum time to rot all oranges.
Multi-source BFS: start from all rotten oranges at once.
Time: O(m*n), Space: O(m*n)
"""
rows, cols = len(grid), len(grid[0])
queue = deque()
fresh = 0
# Find all rotten oranges and count fresh
for r in range(rows):
for c in range(cols):
if grid[r][c] == 2:
queue.append((r, c, 0)) # (row, col, time)
elif grid[r][c] == 1:
fresh += 1
directions = [(0, 1), (0, -1), (1, 0), (-1, 0)]
time = 0
while queue:
r, c, time = queue.popleft()
for dr, dc in directions:
nr, nc = r + dr, c + dc
if 0 <= nr < rows and 0 <= nc < cols and grid[nr][nc] == 1:
grid[nr][nc] = 2
fresh -= 1
queue.append((nr, nc, time + 1))
return time if fresh == 0 else -1

Applications

  1. BFS Traversal - Graphs, trees, shortest path in unweighted graphs
  2. Task Scheduling - CPU scheduling, print spooling
  3. Message Queues - Kafka, RabbitMQ, async processing
  4. Buffering - IO buffers, streaming data
  5. Caching - LRU cache implementation
  6. Rate Limiting - Request throttling

Practice Problems

Easy

ProblemPatternCompanies
Implement Queue using StacksTwo StacksAmazon, Apple
Number of Recent CallsSliding WindowAmazon
Moving Average from Data StreamCircular QueueMeta, Google

Medium

ProblemPatternCompanies
Design Circular QueueCircular BufferAmazon
Task SchedulerPriority QueueMeta, Amazon
Rotting OrangesMulti-source BFSAmazon, Microsoft
Open the LockBFSGoogle, Amazon

Hard

ProblemPatternCompanies
Sliding Window MaximumMonotonic DequeAmazon, Google
Design Hit CounterCircular QueueMeta, Google
Shortest Path in Grid with ObstaclesBFS + StateGoogle

Key Takeaways

  1. FIFO principle - First in, first out
  2. Use deque - Avoid O(n) shift operations with arrays
  3. BFS foundation - Essential for level-order traversal
  4. Circular queue - Fixed-size buffer with wraparound
  5. Priority queue - Process by importance, not order

Next Steps