QueueDequeFIFOBFSSliding WindowMonotonic Deque

Queue & Deque — The Complete Guide

Master queues and deques from first principles. Learn to recognize when FIFO ordering is the key insight, use monotonic deques for sliding window problems, and confidently tackle every queue-based interview question.

45 min read10 sections
01

Intuition from Scratch

Why does this pattern exist?

A queue is the opposite of a stack. Instead of Last-In, First-Out, a queue is First-In, First-Out (FIFO). You add to the back and remove from the front — exactly like a line at a coffee shop. The person who arrived first gets served first.

This sounds trivial, but FIFO ordering is the backbone of some of the most important algorithms in computer science. Breadth-First Search (BFS) — the algorithm that finds shortest paths in unweighted graphs, explores trees level by level, and powers everything from social network friend suggestions to maze solving — is built entirely on a queue. Without FIFO ordering, BFS doesn't work.

A deque (double-ended queue) is the queue's more powerful cousin. It lets you add and remove from both ends in O(1). This makes it perfect for problems where you need to maintain a sliding window of values — you add new elements at one end and expire old elements from the other. The monotonic deque pattern (maintaining sorted order in a deque) is one of the most elegant O(n) solutions in all of DSA.

Analogies to Build Intuition

The Coffee Shop Line

People join at the back and get served from the front. The first person in line is the first to leave. That's FIFO — the fundamental property of a queue. In BFS, nodes are 'served' (explored) in the order they were discovered.

🌊

Ripples in a Pond

Drop a stone in a pond. The ripple expands outward in concentric circles — the closest points are reached first, then the next ring, then the next. BFS explores a graph exactly like this: level by level, nearest nodes first. The queue ensures this order.

🪟

A Sliding Window on a Train

Imagine looking out a train window. As the train moves, new scenery enters from one side and old scenery exits from the other. A deque works the same way — new elements enter from the back, expired elements leave from the front. The monotonic deque keeps track of the 'best view' (min or max) visible through the window at all times.

What kind of problems does it solve?

Queues and deques are your go-to when:

  • You need BFS traversal — shortest path in unweighted graphs, level-order tree traversal, multi-source BFS
  • You need to process items in arrival order — task scheduling, message queues, round-robin
  • You need sliding window min/max — monotonic deque gives O(n) instead of O(n log n)
  • You need to simulate a process — circular queues, hot potato, recent counter
  • You need 0-1 BFS — shortest path with edge weights 0 and 1 using a deque
  • You need to implement other data structures — stack using queues, queue using stacks

The core insight

A queue guarantees FIFO processing — the first item added is the first item removed. This is what makes BFS work: nodes at distance d are always processed before nodes at distance d+1. A deque extends this with O(1) access to both ends, enabling the powerful monotonic deque pattern for sliding window problems.

02

Pattern Recognition

Recognizing when a queue or deque is the right tool is the most important skill. Here are the signals to look for and the traps to avoid.

Keywords & Signals in the Problem Statement

Use Queue / Deque when you see...

  • "Shortest path" in an unweighted graph → BFS with queue
  • "Level-order traversal" of a tree → BFS with queue
  • "Minimum number of steps/moves" → BFS with queue
  • "Rotten oranges" or "walls and gates" → multi-source BFS
  • "Sliding window maximum/minimum" → monotonic deque
  • "Number of recent calls" or "hit counter" → queue with expiry
  • "Implement stack using queues" → queue manipulation
  • "Shortest path with 0/1 weights" → 0-1 BFS with deque
  • "Process tasks in order" or "round-robin scheduling"
  • "Circular buffer" or "design circular queue"

Don't use Queue / Deque when...

  • You need LIFO (last-in, first-out) order — use a Stack
  • You need DFS traversal — use a Stack or recursion
  • You need the Kth largest/smallest — use a Heap
  • The graph has weighted edges (not 0/1) — use Dijkstra with a Heap
  • You need to match nested structures — use a Stack
  • The problem is about next greater/smaller element — use a Monotonic Stack

Queue / Deque vs. Similar Patterns

PatternWhen to UseKey Difference
Queue (FIFO)BFS, level-order traversal, task scheduling, process in arrival orderFirst-in, first-out — guarantees processing in discovery order
Deque (Double-Ended)Sliding window min/max, 0-1 BFS, palindrome checksO(1) add/remove from both ends — more flexible than queue or stack
Stack (LIFO)DFS, bracket matching, undo operations, next greater elementLast-in, first-out — processes most recent item first
Heap / Priority QueueTop-K, shortest path (weighted), merge K sorted listsPriority-based ordering — not FIFO, but min/max first
Monotonic DequeSliding window min/max in O(n)Deque maintaining sorted order within a window — elements expire from front, violators removed from back
Monotonic StackNext greater/smaller element globally (no window constraint)Stack maintaining sorted order — no expiry from front, only pops from top

The queue question to always ask

When you see a graph or grid problem asking for "shortest" or "minimum steps," ask: "Are all edges equal weight?" If yes → BFS with a queue. If edges are 0 or 1 → 0-1 BFS with a deque. If edges have arbitrary weights → Dijkstra with a heap. This single decision tree covers most shortest-path problems.

03

Brute Force → Optimized Thinking

Let's walk through the thinking evolution using a concrete example: Sliding Window Maximum — given an array and a window size k, return the maximum value in each window as it slides from left to right.

1

Brute Force — Scan Each Window

O(n × k) time · O(1) space (excluding output)

For each window position, scan all k elements to find the maximum. When k is large, this is painfully slow — you're re-examining k-1 elements that were already in the previous window.

Brute Forcetypescript
function maxSlidingWindowBrute(nums: number[], k: number): number[] {
  const n = nums.length;
  const result: number[] = [];

  for (let i = 0; i <= n - k; i++) {
    let max = nums[i];
    for (let j = i + 1; j < i + k; j++) {
      max = Math.max(max, nums[j]);
    }
    result.push(max);
  }

  return result;
}
// Problem: For each of (n-k+1) windows, we scan k elements.
// Total: O(n × k). When k ≈ n, this is O(n²).
2

Better — Heap (Max-Heap)

O(n log n) time · O(n) space

Use a max-heap to track the current window's maximum. The max is always at the top. But when the window slides, you need to remove the element that left the window — and heap deletion by value is O(n), not O(log n). Lazy deletion helps but doesn't guarantee O(n log k).

Heap Approachtypescript
// Pseudocode — heap approach
// Push (value, index) into max-heap
// For each window:
//   While heap top's index is outside window → pop (lazy delete)
//   Record heap top as window max
//
// Time: O(n log n) worst case — each element pushed/popped once
// Problem: Lazy deletion means heap can grow to O(n) size.
// Not truly O(n log k). Can we do better?
3

Optimal — Monotonic Deque

O(n) time · O(k) space

The key insight: we don't need ALL elements in the window — only the ones that could potentially be the maximum. If a new element is larger than elements already in the deque, those smaller elements can never be the maximum (the new element will outlast them in the window). Remove them from the back. Expire elements that leave the window from the front.

Monotonic Deque — Optimaltypescript
function maxSlidingWindow(nums: number[], k: number): number[] {
  const deque: number[] = []; // stores indices, front = max
  const result: number[] = [];

  for (let i = 0; i < nums.length; i++) {
    // 1. Remove elements outside the window from the front
    while (deque.length > 0 && deque[0] <= i - k) {
      deque.shift();
    }

    // 2. Remove smaller elements from the back — they can never be max
    while (deque.length > 0 && nums[deque[deque.length - 1]] <= nums[i]) {
      deque.pop();
    }

    // 3. Add current element
    deque.push(i);

    // 4. Record the maximum (front of deque) once window is full
    if (i >= k - 1) {
      result.push(nums[deque[0]]);
    }
  }

  return result;
}
// Each index is pushed once and removed at most once from each end.
// Total operations: O(2n) = O(n). Deque holds at most k elements.

Why Does the Optimization Work?

1

The deque only keeps 'useful' candidates

An element is useful only if it could be the maximum of some future window. If a newer, larger element exists, the older smaller element can never win — it will leave the window before the larger one. So we remove it immediately.

2

The front is always the current maximum

Because we remove smaller elements from the back and expired elements from the front, the deque is always sorted in decreasing order. The front element is the largest in the current window — reading the max is O(1).

3

Each element enters and leaves at most once

Every index is pushed to the back exactly once and removed (from either front or back) at most once. That's O(2n) = O(n) total operations, regardless of k. This amortized analysis is the same insight as monotonic stacks.

The thinking process matters more than the code

In interviews, walk through this progression: "Brute force scans each window — O(nk). A heap gives O(n log n) but has lazy deletion issues. The key insight is that smaller elements in the window are useless once a larger element arrives. A monotonic deque maintains only useful candidates in decreasing order, giving O(n)."

04

Core Templates

Queue and deque problems follow a few recurring templates. Memorize these structures — most problems are variations of one of them.

Template 1: BFS — Level-Order Traversal

The most common queue pattern. Enqueue the starting node(s), then process level by level. Track the level size to know when one level ends and the next begins.

BFS Level-Order Templatetypescript
function bfs(root: TreeNode | null): number[][] {
  if (!root) return [];
  const result: number[][] = [];
  const queue: TreeNode[] = [root];

  while (queue.length > 0) {
    const levelSize = queue.length; // snapshot current level size
    const level: number[] = [];

    for (let i = 0; i < levelSize; i++) {
      const node = queue.shift()!;
      level.push(node.val);

      if (node.left) queue.push(node.left);
      if (node.right) queue.push(node.right);
    }

    result.push(level);
  }

  return result;
}
// When to use: level-order traversal, zigzag traversal,
// right side view, average of levels, minimum depth

Template 2: BFS — Shortest Path in Grid / Graph

Enqueue the start position with distance 0. For each cell, explore all neighbors. The first time you reach the target is the shortest path. Use a visited set to avoid cycles.

BFS Shortest Path Templatetypescript
function shortestPath(grid: number[][], start: [number, number], end: [number, number]): number {
  const rows = grid.length, cols = grid[0].length;
  const queue: [number, number, number][] = [[start[0], start[1], 0]]; // [row, col, dist]
  const visited = new Set<string>();
  visited.add(`${start[0]},${start[1]}`);

  const dirs = [[0, 1], [0, -1], [1, 0], [-1, 0]];

  while (queue.length > 0) {
    const [r, c, dist] = queue.shift()!;

    if (r === end[0] && c === end[1]) return dist;

    for (const [dr, dc] of dirs) {
      const nr = r + dr, nc = c + dc;
      const key = `${nr},${nc}`;

      if (nr >= 0 && nr < rows && nc >= 0 && nc < cols
          && grid[nr][nc] === 0 && !visited.has(key)) {
        visited.add(key);
        queue.push([nr, nc, dist + 1]);
      }
    }
  }

  return -1; // unreachable
}
// When to use: shortest path in maze, word ladder,
// minimum knight moves, open the lock

Template 3: Multi-Source BFS

Instead of starting from one source, enqueue ALL sources at once with distance 0. BFS then expands outward from all sources simultaneously. This finds the shortest distance from any source to every cell.

Multi-Source BFS Templatetypescript
function multiSourceBFS(grid: number[][]): number[][] {
  const rows = grid.length, cols = grid[0].length;
  const dist = Array.from({ length: rows }, () => new Array(cols).fill(-1));
  const queue: [number, number][] = [];

  // Enqueue ALL sources at once
  for (let r = 0; r < rows; r++) {
    for (let c = 0; c < cols; c++) {
      if (grid[r][c] === SOURCE) {
        queue.push([r, c]);
        dist[r][c] = 0;
      }
    }
  }

  const dirs = [[0, 1], [0, -1], [1, 0], [-1, 0]];

  while (queue.length > 0) {
    const [r, c] = queue.shift()!;
    for (const [dr, dc] of dirs) {
      const nr = r + dr, nc = c + dc;
      if (nr >= 0 && nr < rows && nc >= 0 && nc < cols && dist[nr][nc] === -1) {
        dist[nr][nc] = dist[r][c] + 1;
        queue.push([nr, nc]);
      }
    }
  }

  return dist;
}
// When to use: rotting oranges, walls and gates,
// 01 matrix (nearest 0), map of highest peak

Template 4: Monotonic Deque — Sliding Window Min/Max

Maintain a deque of indices in monotonic order (decreasing for max, increasing for min). Remove expired elements from the front. Remove elements that can never win from the back. The front is always the answer.

Monotonic Deque Templatetypescript
function slidingWindowMax(nums: number[], k: number): number[] {
  const deque: number[] = []; // indices in decreasing value order
  const result: number[] = [];

  for (let i = 0; i < nums.length; i++) {
    // Expire: remove indices outside the window from front
    while (deque.length > 0 && deque[0] <= i - k) {
      deque.shift();
    }

    // Maintain monotonic order: remove smaller from back
    while (deque.length > 0 && nums[deque[deque.length - 1]] <= nums[i]) {
      deque.pop();
    }

    deque.push(i);

    // Window is full — record the max (front of deque)
    if (i >= k - 1) {
      result.push(nums[deque[0]]);
    }
  }

  return result;
}
// For sliding window MINIMUM: flip the comparison to >=
// When to use: sliding window maximum, shortest subarray with sum >= k,
// constrained subsequence sum, longest continuous subarray with abs diff <= limit

Which template to pick?

Ask yourself: (1) Am I exploring a graph/tree level by level? → Template 1 or 2. (2) Am I finding shortest distance from multiple sources? → Template 3. (3) Am I finding min/max in a sliding window? → Template 4. (4) Am I processing items in arrival order? → Basic queue.

05

Variations & Sub-Patterns

Queues and deques aren't a single trick — they're a family of techniques. Here are the most common variations and how the approach changes for each.

VariationQueue/Deque StrategyClassic Example
BFS Level-OrderQueue with level-size snapshot per iterationBinary Tree Level Order Traversal, Zigzag Traversal
BFS Shortest PathQueue with (node, distance), visited set, first arrival winsWord Ladder, Open the Lock, Shortest Path in Grid
Multi-Source BFSEnqueue all sources at distance 0, expand outward simultaneouslyRotting Oranges, Walls and Gates, 01 Matrix
0-1 BFSDeque: push 0-weight edges to front, 1-weight edges to backShortest Path in Binary Matrix (with 0/1 weights), Minimum Cost to Make at Least One Valid Path
Monotonic Deque (Max)Decreasing deque — remove smaller from back, expire from frontSliding Window Maximum, Constrained Subsequence Sum
Monotonic Deque (Min)Increasing deque — remove larger from back, expire from frontShortest Subarray with Sum at Least K, Jump Game VI
Circular QueueFixed-size array with head/tail pointers wrapping aroundDesign Circular Queue, Design Circular Deque
Queue SimulationModel real-world FIFO processes — task scheduling, hit countersNumber of Recent Calls, Task Scheduler, Dota2 Senate

Problems mentioned above

Deep Dive: Multi-Source BFS — Rotting Oranges

A grid has fresh oranges (1) and rotten oranges (2). Each minute, rotten oranges rot their 4-directional neighbors. Return the minimum minutes until all oranges are rotten, or -1 if impossible. This is the classic multi-source BFS problem.

Rotting Oranges — Multi-Source BFStypescript
function orangesRotting(grid: number[][]): number {
  const rows = grid.length, cols = grid[0].length;
  const queue: [number, number][] = [];
  let fresh = 0;

  // Enqueue ALL rotten oranges as sources
  for (let r = 0; r < rows; r++) {
    for (let c = 0; c < cols; c++) {
      if (grid[r][c] === 2) queue.push([r, c]);
      if (grid[r][c] === 1) fresh++;
    }
  }

  if (fresh === 0) return 0;

  const dirs = [[0, 1], [0, -1], [1, 0], [-1, 0]];
  let minutes = 0;

  while (queue.length > 0 && fresh > 0) {
    const size = queue.length;
    minutes++;

    for (let i = 0; i < size; i++) {
      const [r, c] = queue.shift()!;
      for (const [dr, dc] of dirs) {
        const nr = r + dr, nc = c + dc;
        if (nr >= 0 && nr < rows && nc >= 0 && nc < cols && grid[nr][nc] === 1) {
          grid[nr][nc] = 2; // rot it
          fresh--;
          queue.push([nr, nc]);
        }
      }
    }
  }

  return fresh === 0 ? minutes : -1;
}
// Time: O(rows × cols) — each cell processed at most once
// Space: O(rows × cols) — queue can hold all cells
// Key: ALL rotten oranges start in the queue at time 0.
// BFS expands one "ring" per minute — exactly like ripples in a pond.

Deep Dive: 0-1 BFS — Shortest Path with 0/1 Weights

When edge weights are only 0 or 1, you don't need Dijkstra. Use a deque: push 0-weight edges to the front (they don't increase distance) and 1-weight edges to the back (they do). This gives O(V + E) instead of O((V + E) log V).

0-1 BFS Templatetypescript
function zeroOneBFS(graph: Map<number, [number, number][]>, start: number, n: number): number[] {
  const dist = new Array(n).fill(Infinity);
  dist[start] = 0;
  const deque: number[] = [start];

  while (deque.length > 0) {
    const u = deque.shift()!;

    for (const [v, weight] of graph.get(u) ?? []) {
      const newDist = dist[u] + weight;
      if (newDist < dist[v]) {
        dist[v] = newDist;
        if (weight === 0) {
          deque.unshift(v); // 0-weight → push to FRONT
        } else {
          deque.push(v);    // 1-weight → push to BACK
        }
      }
    }
  }

  return dist;
}
// Time: O(V + E) — same as regular BFS
// Space: O(V) for distance array + deque
// Key: 0-weight edges go to front (processed immediately),
// 1-weight edges go to back (processed later).
// This maintains the BFS invariant: deque is sorted by distance.

Deep Dive: Monotonic Deque — Jump Game VI

Given an array and a jump length k, start at index 0 and reach the last index. At each step, you can jump 1 to k positions forward. Your score is the sum of values at visited indices. Maximize the score. This combines DP with a monotonic deque.

Jump Game VI — DP + Monotonic Dequetypescript
function maxResult(nums: number[], k: number): number {
  const n = nums.length;
  const dp = new Array(n).fill(0);
  dp[0] = nums[0];
  const deque: number[] = [0]; // indices, decreasing dp values

  for (let i = 1; i < n; i++) {
    // Expire: remove indices outside the jump window
    while (deque.length > 0 && deque[0] < i - k) {
      deque.shift();
    }

    // dp[i] = nums[i] + max(dp[j]) for j in [i-k, i-1]
    dp[i] = nums[i] + dp[deque[0]];

    // Maintain decreasing order: remove smaller dp values from back
    while (deque.length > 0 && dp[deque[deque.length - 1]] <= dp[i]) {
      deque.pop();
    }

    deque.push(i);
  }

  return dp[n - 1];
}
// Time: O(n) — each index pushed/popped at most once
// Space: O(n) for dp array, O(k) for deque
// Key: the deque maintains the maximum dp value within the jump window.
// This is the "DP + monotonic deque" pattern — very common in hard problems.
06

Visual Walkthrough

Let's trace through three classic queue/deque problems step by step to see the pattern in action.

BFS Level-Order Traversal — Step by Step

Level-Order Traversal — Tracetext
Tree:
        3
       / \
      9   20
         /  \
        15   7

queue = [3]

Level 0: levelSize = 1
  Dequeue 3level = [3]
  Enqueue children: 9, 20
  queue = [9, 20]
  result = [[3]]

Level 1: levelSize = 2
  Dequeue 9level = [9]    (no children)
  Dequeue 20level = [9, 20]
  Enqueue children: 15, 7
  queue = [15, 7]
  result = [[3], [9, 20]]

Level 2: levelSize = 2
  Dequeue 15level = [15]  (no children)
  Dequeue 7level = [15, 7] (no children)
  queue = []
  result = [[3], [9, 20], [15, 7]]

Queue emptydone.

Answer: [[3], [9, 20], [15, 7]]

Key: The "levelSize = queue.length" snapshot at the start of each
iteration tells you exactly how many nodes belong to the current level.
Everything enqueued during this iteration belongs to the NEXT level.

Rotting Oranges (Multi-Source BFS) — Step by Step

Rotting Oranges — Tracetext
Grid:        2  1  1
             1  1  0
             0  1  1

Initial: queue = [(0,0)], fresh = 6

Minute 1: process (0,0)
  Rot (0,1) and (1,0)
  queue = [(0,1), (1,0)], fresh = 4
  Grid:  2  2  1
         2  1  0
         0  1  1

Minute 2: process (0,1) and (1,0)
  (0,1) rots (0,2) and (1,1)
  (1,0) has no fresh neighbors (already rotten or wall)
  queue = [(0,2), (1,1)], fresh = 2
  Grid:  2  2  2
         2  2  0
         0  1  1

Minute 3: process (0,2) and (1,1)
  (0,2) has no fresh neighbors
  (1,1) rots (2,1)
  queue = [(2,1)], fresh = 1
  Grid:  2  2  2
         2  2  0
         0  2  1

Minute 4: process (2,1)
  (2,1) rots (2,2)
  queue = [(2,2)], fresh = 0
  Grid:  2  2  2
         2  2  0
         0  2  2

fresh = 0done!

Answer: 4 minutes

Key: ALL rotten oranges start in the queue simultaneously.
Each "minute" is one BFS level. The answer is the number of levels.

Sliding Window Maximum (Monotonic Deque) — Step by Step

Sliding Window Maximum — Tracetext
Input: nums = [1, 3, -1, -3, 5, 3, 6, 7], k = 3
deque = [] (stores indices), result = []

i=0: num=1
  deque emptypush 0
  deque = [0]                    (values: [1])
  window not full yet

i=1: num=3
  nums[0]=1 ≤ 3 → pop 0 from back
  push 1
  deque = [1]                    (values: [3])
  window not full yet

i=2: num=-1
  nums[1]=3 > -1 → keep
  push 2
  deque = [1, 2]                 (values: [3, -1])
  window full → result.push(nums[1]) = 3
  result = [3]

i=3: num=-3
  deque[0]=1, 1 > 3-3=0 → not expired
  nums[2]=-1 > -3 → keep
  push 3
  deque = [1, 2, 3]             (values: [3, -1, -3])
  result.push(nums[1]) = 3
  result = [3, 3]

i=4: num=5
  deque[0]=1, 1 ≤ 4-3=1 → EXPIRE, shift
  nums[2]=-1 ≤ 5 → pop from back
  nums[3]=-3 ≤ 5 → pop from back
  push 4
  deque = [4]                    (values: [5])
  result.push(nums[4]) = 5
  result = [3, 3, 5]

i=5: num=3
  nums[4]=5 > 3 → keep
  push 5
  deque = [4, 5]                 (values: [5, 3])
  result.push(nums[4]) = 5
  result = [3, 3, 5, 5]

i=6: num=6
  nums[5]=3 ≤ 6 → pop from back
  nums[4]=5 ≤ 6 → pop from back
  push 6
  deque = [6]                    (values: [6])
  result.push(nums[6]) = 6
  result = [3, 3, 5, 5, 6]

i=7: num=7
  nums[6]=6 ≤ 7 → pop from back
  push 7
  deque = [7]                    (values: [7])
  result.push(nums[7]) = 7
  result = [3, 3, 5, 5, 6, 7]

Answer: [3, 3, 5, 5, 6, 7]

Key: The deque always holds indices in DECREASING value order.
Front = current window max. Back = where new elements enter.
Smaller elements are removed from back (they can never be max).
Expired elements are removed from front (they left the window).
07

Practice Problems

These 7 problems are carefully ordered from easy to hard. Each one teaches a different aspect of queue/deque-based problem solving. Solve them in order.

1

LC 933. Number of Recent Calls

Easy

Why this pattern:

Basic queue with expiry. Each ping adds a timestamp to the queue. Remove timestamps older than 3000ms from the front. The queue size is the answer. This is the 'hello world' of queues.

Key idea: Push the new timestamp to the back. While the front timestamp is older than t - 3000, dequeue it. Return queue.length. The queue naturally maintains a sliding window of recent timestamps.

2

LC 102. Binary Tree Level Order Traversal

Medium

Why this pattern:

BFS with level-size snapshot. The foundational BFS-on-trees problem. Enqueue the root, then process level by level using the 'levelSize = queue.length' trick to separate levels.

Key idea: At the start of each level, snapshot the queue size. Process exactly that many nodes (they're all on the same level). Their children go into the queue for the next level. This cleanly separates levels without extra markers.

3

LC 994. Rotting Oranges

Medium

Why this pattern:

Multi-source BFS. All rotten oranges are sources at time 0. BFS expands one ring per minute. The number of BFS levels is the answer. Count fresh oranges to detect the impossible case.

Key idea: Enqueue all rotten oranges at once. Process level by level (each level = 1 minute). For each rotten orange, rot its fresh neighbors and enqueue them. Track fresh count — if it's not 0 at the end, return -1.

4

LC 752. Open the Lock

Medium

Why this pattern:

BFS shortest path in an implicit graph. Each lock state is a node. Each turn of a wheel is an edge. BFS finds the minimum turns from '0000' to the target, avoiding deadends.

Key idea: Start BFS from '0000'. For each state, generate all 8 neighbors (4 wheels × 2 directions). Skip deadends and visited states. The first time you reach the target is the shortest path. Use a Set for O(1) visited checks.

5

LC 239. Sliding Window Maximum

Hard

Why this pattern:

Monotonic deque (decreasing). The deque holds indices in decreasing value order. The front is always the current window's maximum. Remove expired indices from front, remove smaller values from back.

Key idea: Three operations per element: (1) expire from front if outside window, (2) remove from back if smaller than current (they can never be max), (3) push current to back. Front of deque = window max. Each element enters and leaves at most once → O(n).

6

LC 1696. Jump Game VI

Medium

Why this pattern:

DP + monotonic deque. dp[i] = nums[i] + max(dp[j]) for j in [i-k, i-1]. The monotonic deque maintains the maximum dp value within the jump window, turning an O(nk) DP into O(n).

Key idea: For each index, the deque front gives the best dp value within the jump range. Compute dp[i], then maintain the deque: expire old indices from front, remove smaller dp values from back, push i. This is the 'DP optimization with monotonic deque' pattern.

7

LC 862. Shortest Subarray with Sum at Least K

Hard

Why this pattern:

Prefix sums + monotonic deque (increasing). The deque maintains indices with increasing prefix sums. For each index, check if prefix[i] - prefix[deque.front] >= K (found a valid subarray — pop front and update answer). Remove from back if prefix sum is not increasing.

Key idea: Compute prefix sums. The deque holds indices in increasing prefix-sum order. For each i: (1) while prefix[i] - prefix[front] >= K, update answer and pop front (shorter subarrays might exist further right). (2) Remove from back if prefix[back] >= prefix[i] (they're dominated). This handles negative numbers correctly.

Practice strategy

For each problem: (1) Identify whether it's BFS, multi-source BFS, or monotonic deque before coding. (2) Ask "what am I enqueuing and when am I dequeuing?" (3) After solving, write one sentence: "The queue holds [what] and dequeues when [condition]."

08

Common Mistakes

These are the traps that catch people on queue/deque problems. Learn them here so you don't learn them in an interview.

📏

Not snapshotting the level size in BFS

You process nodes in a while loop without tracking how many belong to the current level. New children get mixed into the same iteration, and you can't separate levels.

Always snapshot: const levelSize = queue.length at the START of each level iteration. Then loop exactly levelSize times. Everything enqueued during this loop belongs to the next level. This is the #1 BFS mistake.

🔄

Using Array.shift() in JavaScript for large queues

Array.shift() is O(n) because it re-indexes every element. For BFS on large graphs, this turns your O(V + E) algorithm into O(V² + E). The queue becomes the bottleneck.

Use an index pointer instead of shift(): let front = 0; and access queue[front++] instead of queue.shift(). Or use a proper deque implementation. In interviews, mention this optimization — it shows you understand JS internals.

👻

Forgetting to mark visited BEFORE enqueuing in BFS

You mark nodes as visited when you dequeue them instead of when you enqueue them. This causes the same node to be enqueued multiple times by different parents, wasting time and potentially giving wrong shortest-path distances.

Mark visited immediately when you enqueue, not when you dequeue. This prevents duplicate entries. The rule: 'visit on enqueue, not on dequeue.' This is especially critical in grid BFS where 4 neighbors might all try to enqueue the same cell.

↔️

Confusing monotonic deque direction

You use a decreasing deque when you need increasing (or vice versa). Sliding window MAX needs a decreasing deque (largest at front). Sliding window MIN needs an increasing deque (smallest at front).

Think about what the front should hold: for MAX, the front should be the largest → remove smaller elements from back → decreasing deque. For MIN, the front should be the smallest → remove larger elements from back → increasing deque.

🪟

Off-by-one in window expiry

You expire elements from the deque front using deque[0] < i - k instead of deque[0] <= i - k (or vice versa). This either keeps expired elements or removes valid ones, giving wrong answers.

For a window of size k ending at index i, valid indices are [i-k+1, i]. So expire when deque[0] < i - k + 1, which is the same as deque[0] <= i - k. Draw out a small example with k=3 to verify your condition.

🎯

Using BFS when edges have different weights

You use a regular BFS queue for a graph with weighted edges. BFS only guarantees shortest path when all edges have equal weight. With different weights, BFS can find a longer path first.

Equal weights → BFS (queue). Weights 0 and 1 → 0-1 BFS (deque). Arbitrary non-negative weights → Dijkstra (min-heap). Negative weights → Bellman-Ford. Always check the edge weight constraint before choosing your algorithm.

09

Interview Strategy

Knowing when and how to use a queue or deque is half the battle. Here's how to present it in an interview setting to maximize your score.

The 5-Step Interview Flow

1

Clarify & Identify the Pattern

'This is asking for the minimum number of steps in an unweighted graph — that's BFS with a queue.' Or: 'This needs the maximum in a sliding window — that's a monotonic deque.' Name the pattern early.

2

State the Brute Force

'The brute force would check every element in each window — O(nk). But I can maintain a deque of candidates in decreasing order, so the max is always at the front — O(n).' Show you understand the baseline.

3

Explain What the Queue/Deque Holds

'The queue holds (node, distance) pairs. Nodes are enqueued when discovered and dequeued when processed. BFS guarantees the first time we reach a node is the shortest path.' This is the key insight interviewers want.

4

Code It Clean

Use descriptive variable names (levelSize, not n). Comment the three deque operations (expire, maintain order, push). Handle edge cases (empty graph, single node, window size 1) explicitly.

5

Test & Analyze

Trace through a small example. For BFS: 'Starting from node A, we enqueue B and C at distance 1, then D at distance 2...' State complexity: 'O(V + E) time for BFS, O(n) for monotonic deque since each element is pushed and popped at most once.'

Common Follow-Up Questions

Follow-UpHow to Handle
"Can you do BFS without a queue?"Technically yes — you can use two arrays (current level and next level) and swap them. But a queue is cleaner and more standard. Mention this as an alternative if asked.
"What if the graph has weighted edges?"BFS only works for equal-weight edges. For 0/1 weights → 0-1 BFS with deque. For arbitrary non-negative weights → Dijkstra with min-heap. For negative weights → Bellman-Ford.
"Why not use DFS for shortest path?"DFS doesn't guarantee shortest path in unweighted graphs — it might find a longer path first. BFS explores level by level, so the first time it reaches a node is always the shortest path. DFS is for exhaustive search, BFS is for shortest path.
"Can you optimize the Array.shift() in JavaScript?"Yes — use a front pointer: let front = 0, then queue[front++] instead of shift(). This avoids O(n) re-indexing. Or use a linked list. In production, use a proper deque library. Mention this to show you understand JS performance.
"What's the difference between monotonic stack and monotonic deque?"Monotonic stack: elements only leave from the top. Used for 'next greater/smaller' globally. Monotonic deque: elements can leave from both ends. Used for 'min/max within a sliding window.' The deque adds expiry from the front.
"How would you handle bidirectional BFS?"Start BFS from both source and target simultaneously. Alternate expanding the smaller frontier. When the two frontiers meet, the sum of their distances is the shortest path. This can reduce time from O(b^d) to O(b^(d/2)).

The meta-skill

Interviewers love queue problems because they test your understanding of BFS guarantees and your ability to model problems as graphs. The key sentence to say is: "BFS with a queue guarantees shortest path because it processes nodes in order of discovery distance — all nodes at distance d are processed before any node at distance d+1." For deque problems: "Each element is pushed and popped at most once from each end, so the total time is O(n)."

10

Summary & Cheat Sheet

Pin this section. Review it before mock interviews and contests.

Quick Revision Cheat Sheet

Queue core idea: FIFO — first item added is the first removed. This is what makes BFS work: nodes at distance d are processed before distance d+1.

Deque core idea: O(1) add/remove from both ends. Enables monotonic deque for sliding window min/max and 0-1 BFS.

BFS guarantee: In an unweighted graph, BFS finds the shortest path. The queue ensures level-by-level exploration.

Level-order trick: Snapshot levelSize = queue.length at the start of each level. Process exactly that many nodes. Children go to the next level.

Multi-source BFS: Enqueue ALL sources at distance 0. BFS expands outward from all sources simultaneously. Used for 'nearest distance from any source.'

0-1 BFS: Deque: 0-weight edges → push to front. 1-weight edges → push to back. O(V+E) instead of Dijkstra's O((V+E) log V).

Monotonic deque (max): Decreasing order. Remove smaller from back (they can never be max). Expire from front (left the window). Front = current max.

Monotonic deque (min): Increasing order. Remove larger from back (they can never be min). Expire from front (left the window). Front = current min.

Visit on enqueue: Mark nodes visited when you ENQUEUE them, not when you dequeue. Prevents duplicate entries and wrong distances.

JS shift() trap: Array.shift() is O(n). Use a front pointer or proper deque for large inputs. Mention this in interviews.

Amortized O(n): Monotonic deque: each element pushed once, popped at most once from each end → O(2n) = O(n) total.

DP + deque: When a DP recurrence needs max/min over a sliding window of previous states, monotonic deque optimizes from O(nk) to O(n).

Mental trigger: "Shortest path" or "minimum steps" → BFS queue. "Sliding window min/max" → monotonic deque. "Level by level" → BFS queue.

Decision Flowchart

When to Use Queue / Deque — Decision Treetext
Does the problem ask for shortest path or minimum steps?
├── YESAre all edge weights equal?
│         ├── YESBFS with a Queue (Template 2)
│         └── NOAre weights only 0 and 1?
│                   ├── YES0-1 BFS with a Deque
│                   └── NODijkstra with a Heap (not a queue problem)
└── NODoes the problem ask for level-by-level processing?
          ├── YESBFS Level-Order with a Queue (Template 1)
          └── NODoes the problem involve multiple starting points expanding outward?
                    ├── YESMulti-Source BFS (Template 3)
                    └── NODoes the problem need min/max in a sliding window?
                              ├── YESMonotonic Deque (Template 4)
                              └── NODoes a DP recurrence need max/min over a range of previous states?
                                        ├── YESDP + Monotonic Deque
                                        └── NODoes the problem involve FIFO processing or simulation?
                                                  ├── YESBasic Queue
                                                  └── NOQueue/Deque probably isn't the right pattern
Consider Stack, Heap, or Two Pointers

Queue & Deque Operations Quick Reference

OperationQueueDequeCommon Pitfall
Add to backenqueue / push — O(1)pushBack — O(1)N/A
Remove from frontdequeue / shift — O(1)*popFront — O(1)JS shift() is O(n) on arrays
Add to frontN/ApushFront / unshift — O(1)JS unshift() is O(n) on arrays
Remove from backN/ApopBack / pop — O(1)Forgetting this is what makes deque special
Peek frontO(1)O(1)Peeking without checking empty
Peek backN/AO(1)Only available on deque, not queue
Size / isEmptyO(1)O(1)Off-by-one in size checks

One last thing

The queue is the unsung hero of graph algorithms. BFS — powered by a simple FIFO queue — solves shortest path, level-order traversal, connected components, and topological sort. The deque elevates this further: monotonic deques turn O(nk) sliding window problems into O(n), and 0-1 BFS handles binary-weighted graphs without a heap. Master the queue and deque, and you've unlocked an entire category of interview problems that many candidates struggle with.