A queue (FIFO) makes BFS explore level by level — first seen, first processed.
Focus on recognizing:
“Level by level” / “shortest path in unweighted graph” → queue
Pattern 1: Tree Level Order
Level order on [3,9,20,null,null,15,7] — each grid row is one queue drain. Press ▶ to animate.
⚠️ Animation & Content Notice
The animation work is not fully finished — some animations may have slight errors.
If there is a major error in the content or if the animation or content is difficult to understand, please contact us at rayyancodingschool@gmail.com.
Level-Order Traversal (BFS on a Tree)
Use a queue to visit a binary tree level by level. Freeze the queue size at the start of each round so you process exactly one level before the next round's children are added.
Tree [3,9,20,null,null,15,7]. Seed the queue with the root. Each round: note the current size, poll that many nodes, visit them, and enqueue their children. Watch the queue hold one whole level at a time — the level order [3,9,20,15,7] emerges naturally.
1
queue = [root]
2
while queue not empty:
3
size = queue.length // freeze level
4
repeat size times:
5
node = queue.poll(); visit node
6
enqueue node.left, node.right
public List<List<Integer>> levelOrder(TreeNode root) {
List<List<Integer>> result = new ArrayList<>();
if (root == null) return result;
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(root);
while (!queue.isEmpty()) {
int size = queue.size(); // freeze the level
List<Integer> level = new ArrayList<>();
for (int i = 0; i < size; i++) {
TreeNode node = queue.poll();
level.add(node.val);
if (node.left != null) queue.offer(node.left);
if (node.right != null) queue.offer(node.right);
}
result.add(level);
}
return result;
}from collections import deque
def level_order(root):
if not root:
return []
result = []
queue = deque([root])
while queue:
size = len(queue) # freeze the level
level = []
for _ in range(size):
node = queue.popleft()
level.append(node.val)
if node.left:
queue.append(node.left)
if node.right:
queue.append(node.right)
result.append(level)
return resultvector<vector<int>> levelOrder(TreeNode* root) {
vector<vector<int>> result;
if (!root) return result;
queue<TreeNode*> q;
q.push(root);
while (!q.empty()) {
int size = q.size(); // freeze the level
vector<int> level;
for (int i = 0; i < size; i++) {
TreeNode* node = q.front();
q.pop();
level.push_back(node->val);
if (node->left) q.push(node->left);
if (node->right) q.push(node->right);
}
result.push_back(level);
}
return result;
}function levelOrder(root) {
if (!root) return [];
const result = [];
const queue = [root];
while (queue.length) {
const size = queue.length; // freeze the level
const level = [];
for (let i = 0; i < size; 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;
}Capture
queue.size()BEFORE the inner loop — children enqueued mid-loop belong to the NEXT level.
Pattern 2: Graph BFS (Shortest Path)
The queue explores in rings; first arrival = fewest edges.
⚠️ Animation & Content Notice
The animation work is not fully finished — some animations may have slight errors.
If there is a major error in the content or if the animation or content is difficult to understand, please contact us at rayyancodingschool@gmail.com.
BFS Shortest Path (Graph)
Breadth-First Search explores a graph level by level using a queue. The first time a node is reached, it is via the fewest edges — so BFS finds the shortest path in an unweighted graph in O(V + E).
Graph A→F. Seed the queue with A. Each step, dequeue a node and enqueue its unvisited neighbors. Watch the queue hold one 'ring' of the graph at a time, and the edges light up as discovery spreads. The distance to F is the number of rings popped before F is dequeued.
1
queue = [start]; dist[start] = 0
2
while queue:
3
n = dequeue
4
for nb in adj[n] not visited:
5
dist[nb] = dist[n]+1; enqueue(nb)
Unweighted edges ⇒ BFS distance = shortest distance:
public int shortestPath(List<List<Integer>> graph,
int start, int end) {
boolean[] visited = new boolean[graph.size()];
Queue<int[]> queue = new LinkedList<>(); // [node, dist]
queue.offer(new int[]{start, 0});
visited[start] = true;
while (!queue.isEmpty()) {
int[] cur = queue.poll();
if (cur[0] == end) return cur[1];
for (int next : graph.get(cur[0])) {
if (!visited[next]) { // mark ON enqueue
visited[next] = true;
queue.offer(new int[]{next, cur[1] + 1});
}
}
}
return -1;
}from collections import deque
def shortest_path(graph, start, end):
visited = [False] * len(graph)
queue = deque([(start, 0)])
visited[start] = True
while queue:
node, dist = queue.popleft()
if node == end:
return dist
for nxt in graph[node]:
if not visited[nxt]: # mark ON enqueue
visited[nxt] = True
queue.append((nxt, dist + 1))
return -1int shortestPath(vector<vector<int>>& graph, int start, int end) {
vector<bool> visited(graph.size(), false);
queue<pair<int, int>> q; // {node, dist}
q.push({start, 0});
visited[start] = true;
while (!q.empty()) {
auto [node, dist] = q.front();
q.pop();
if (node == end) return dist;
for (int next : graph[node]) {
if (!visited[next]) { // mark ON enqueue
visited[next] = true;
q.push({next, dist + 1});
}
}
}
return -1;
}function shortestPath(graph, start, end) {
const visited = new Array(graph.length).fill(false);
const queue = [[start, 0]];
visited[start] = true;
while (queue.length) {
const [node, dist] = queue.shift();
if (node === end) return dist;
for (const next of graph[node]) {
if (!visited[next]) {
// mark ON enqueue
visited[next] = true;
queue.push([next, dist + 1]);
}
}
}
return -1;
}Mark visited when ENQUEUING, not when popping — otherwise nodes enter the queue twice.
Pattern 3: Multi-Source BFS
Seed ALL sources together — they share the waves and halve the time.
⚠️ Animation & Content Notice
The animation work is not fully finished — some animations may have slight errors.
If there is a major error in the content or if the animation or content is difficult to understand, please contact us at rayyancodingschool@gmail.com.
Rotting Oranges (Multi-Source BFS)
Every rotten orange enters the queue simultaneously. Each minute, rot spreads to adjacent fresh oranges. Find the minimum minutes until no fresh orange remains.
Grid: R=rotten, O=fresh, F=wall. Seed ALL rotten cells at minute 0. Each wave spreads rot one cell outward. Watch cells change from O to R as waves propagate.
1
queue = all initially rotten
2
minute = 0
3
while queue not empty:
4
process whole level, minute++
5
fresh neighbours → rot & enqueue
Seed the queue with ALL sources; distances become “nearest source”:
public int[][] nearestExit(int[][] grid) {
int rows = grid.length, cols = grid[0].length;
Queue<int[]> queue = new LinkedList<>();
for (int r = 0; r < rows; r++)
for (int c = 0; c < cols; c++)
if (grid[r][c] == 0) // every source
queue.offer(new int[]{r, c});
int[][] dirs = {{1,0},{-1,0},{0,1},{0,-1}};
while (!queue.isEmpty()) {
int[] cell = queue.poll();
for (int[] d : dirs) {
int nr = cell[0] + d[0], nc = cell[1] + d[1];
if (nr >= 0 && nr < rows && nc >= 0 && nc < cols
&& grid[nr][nc] == -1) { // unvisited land
grid[nr][nc] = grid[cell[0]][cell[1]] + 1;
queue.offer(new int[]{nr, nc});
}
}
}
return grid;
}from collections import deque
def nearest_exit(grid):
rows, cols = len(grid), len(grid[0])
queue = deque()
for r in range(rows):
for c in range(cols):
if grid[r][c] == 0: # every source
queue.append((r, c))
while queue:
r, c = queue.popleft()
for dr, dc in ((1, 0), (-1, 0), (0, 1), (0, -1)):
nr, nc = r + dr, c + dc
if 0 <= nr < rows and 0 <= nc < cols \
and grid[nr][nc] == -1: # unvisited land
grid[nr][nc] = grid[r][c] + 1
queue.append((nr, nc))
return gridvector<vector<int>> nearestExit(vector<vector<int>>& grid) {
int rows = grid.size(), cols = grid[0].size();
queue<pair<int,int>> q;
for (int r = 0; r < rows; r++)
for (int c = 0; c < cols; c++)
if (grid[r][c] == 0) // every source
q.push({r, c});
int dirs[4][2] = {{1,0},{-1,0},{0,1},{0,-1}};
while (!q.empty()) {
auto [r, c] = q.front();
q.pop();
for (auto& d : dirs) {
int nr = r + d[0], nc = c + d[1];
if (nr >= 0 && nr < rows && nc >= 0 && nc < cols
&& grid[nr][nc] == -1) { // unvisited
grid[nr][nc] = grid[r][c] + 1;
q.push({nr, nc});
}
}
}
return grid;
}function nearestExit(grid) {
const rows = grid.length,
cols = grid[0].length;
const queue = [];
for (let r = 0; r < rows; r++)
for (let c = 0; c < cols; c++)
if (grid[r][c] === 0) queue.push([r, c]); // every source
const dirs = [
[1, 0],
[-1, 0],
[0, 1],
[0, -1],
];
while (queue.length) {
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] = grid[r][c] + 1;
queue.push([nr, nc]);
}
}
}
return grid;
}Common Mistakes
Using a stack instead of a queue.
LIFO turns BFS into DFS — you lose level boundaries and shortest-path guarantees.
Marking visited at pop time.
The same node gets enqueued multiple times before being marked — exponential blowup on dense graphs.
Recomputing the level size inside the loop.
size must be frozen once per level; queue.length changes as you enqueue children.
Complexity
| Graph shape | Time | Space |
|---|---|---|
| Tree | O(n) | O(width) |
| Graph V, E | O(V + E) | O(V) |
| Grid n×m | O(n·m) | O(n·m) |
Premium Content
Unlock BFS with Queue and all premium lessons with a subscription.
From ₹199.99/year — See plans