BFS Shortest Path (Unweighted)
Shortest path in an unweighted graph: BFS from the source, record parents, then walk parents back from the target to reconstruct the path.
Overview
When every edge costs the same, the shortest path is the one with the fewest edges, and Breadth-First Search (BFS) computes it exactly: the first time BFS reaches a node is along a minimum-hop path. Storing parent[v] at discovery time lets you rebuild the actual path by walking from the target back to the source and reversing.
Preconditions: unweighted (or all weights equal) edges, directed or undirected. Complexity O(V + E) — no heap, no log factor. This is the cheapest shortest-path algorithm there is, so always ask "are the edges really weighted?" before reaching for Dijkstra's Algorithm.
Intuition
A mental model before the formal terms.
Imagine everyone in a social network forwarding a message to all their friends once per day. The day a person first receives it is their distance from the origin, and the friend they heard it from first is their parent. To find how the message reached you, ask "who told you?" repeatedly until you reach the sender — that chain is a shortest path.
How it works
- Run BFS from
swithdist[s] = 0,parent[s] = -1. When discoveringvfromu, setdist[v] = dist[u] + 1,parent[v] = u. - Optionally stop as soon as
tis dequeued (or even enqueued) — every node still in the queue is at distance ≥dist[t]. - If
twas never reached, there is no path. Otherwise, start attand followparentpointers until-1, collecting nodes; reverse to gets → … → t. - For grids, "nodes" are cells and "edges" are the 4 (or 8) neighbour moves; the adjacency list is generated on the fly from a delta array.
Why it works
BFS dequeues nodes in non-decreasing distance order, so parent[v] is a node at distance dist[v] - 1. Following parents therefore decreases distance by exactly 1 per step and reaches s after dist[t] steps: the reconstructed path has length dist[t].
No shorter path can exist: any path of length k from s to t would have put t in level ≤ k, and BFS records the minimal level.
Recognition
How to tell a problem wants this.
- "Minimum number of moves/steps/transformations" where each move has the same cost.
- Grid with obstacles: "shortest path from top-left to bottom-right".
- Implicit graphs: word ladder, knight moves, sliding puzzle, lock combinations.
- The problem asks for the path itself, not just its length — you need
parent.
Interactive visualization
Play, step, change the input. ← → and space work too.
1queue = [source]; parent = {source: None}2while queue not empty:3 u = queue.popleft()4 if u == target: break5 for v in neighbors(u):6 if v not in parent:7 parent[v] = u; queue.append(v)8path = follow parent from target back to source, reversedPseudocode
1dist[s] = 0; parent[s] = -1; queue = [s]2while queue not empty:3 u = queue.popleft(); if u == t: break4 for v in adj[u]:5 if dist[v] undefined: dist[v] = dist[u] + 1; parent[v] = u; queue.append(v)6if dist[t] undefined: return none7path = []; cur = t; while cur != -1: path.append(cur); cur = parent[cur]8return reversed(path)Implementations
1from collections import deque2 3 4def bfs_shortest_path(adj: list[list[int]], s: int, t: int) -> list[int] | None:5 """Fewest-edges path from s to t, or None if unreachable. Nodes are 0..n-1."""61 · Initialize parents and queue7 n = len(adj)8 parent = [-1] * n9 seen = [False] * n10 seen[s] = True11 q = deque([s])122 · BFS with early exit13 while q:14 u = q.popleft()15 if u == t:16 break173 · Discover neighbours18 for v in adj[u]:19 if not seen[v]:20 seen[v] = True21 parent[v] = u22 q.append(v)234 · Reconstruct path24 if not seen[t]:25 return None26 path = []27 cur = t28 while cur != -1:29 path.append(cur)30 cur = parent[cur]31 path.reverse()32 return pathdequefor O(1)popleft().breakas soon astis popped;seen[t]tells afterwards whether it was ever discovered.Nonesignals unreachable;list[int] | Noneis the Python 3.10+ union syntax.- The parent walk appends nodes from
tback tos, thenpath.reverse()fixes the order in place.
path[::-1]would also work but creates a copy;list.reverse()is in place.- Type hint
list[int] | Nonerequires Python 3.10; useOptional[list[int]]before that. - For grid problems, generate neighbours on the fly from a delta list instead of materialising
adj.
- Using
list.pop(0). - Testing
if not path:to detect unreachable when the path could legitimately be empty — returnNoneand testis None. - Forgetting to mark
sseen before the loop, so it can be re-enqueued through a cycle.
- Unreachable sentinel: C++ returns an empty vector, JS/TS return
null, Python returnsNone. The typed languages (TS strict mode) force the caller to handle it. - Reversal: C++
std::reverse(begin, end), JS/TSArray.prototype.reverse()(in place), Pythonlist.reverse()(in place) or[::-1](copy). - Queue: only JS/TS lack a built-in O(1) deque, hence the head index.
Complexity
Best case: target is the source or an immediate neighbour and the search stops early. Path reconstruction is O(path length) ≤ O(V).
Compare growth rates in the Complexity Explorer →When to use — and when not to
- All edges have equal weight — this beats Dijkstra by a log factor and is simpler.
- Grid mazes, puzzles, and word-transformation problems (implicit unweighted graphs).
- You need distances from one source to all nodes in a single pass.
- The graph is huge but the target is expected to be close: early exit keeps the explored region small.
- Edges have different positive weights — use Dijkstra's Algorithm; weights 0 or 1 only — 0-1 BFS.
- Negative weights — Bellman-Ford.
- All-pairs on a dense graph — running BFS from every node is
O(V·(V+E)); fine for sparse graphs but compare with Floyd-Warshall for dense ones. - Only path existence matters, not length — Depth-First Search (DFS) or Union-Find (Disjoint Set Union) may be simpler.
Alternatives
Common mistakes
- Reconstructing the path from
sforward instead of fromtbackward — parents only point toward the source. - Checking
u == tonly at dequeue but also updatingdistfor nodes already visited, corrupting distances. - Breaking on discovery of
tbut then readingdist[t]from an uninitialised entry because it was set after the check. - On grids, forgetting that the start cell may itself be blocked, or that the answer counts cells rather than edges (off by one).
- Bidirectional BFS bookkeeping errors: the meeting node must be checked when expanding, and the smaller frontier should be expanded first.
Interview patterns
- Shortest path in binary matrix with 8-directional moves.
- Word ladder: BFS over words; generate neighbours by substituting each position with a–z,
O(L · 26)per word. - Bidirectional BFS to cut the explored region from
b^dto roughly2·b^(d/2). - State-augmented BFS: node = (cell, keys collected) or (cell, obstacles removed so far) when a small extra dimension changes reachability.
- Multi-source BFS for "distance to nearest 0" (01 matrix).
- Course ScheduleIntermediate
- Network Delay TimeAdvanced
- Number of IslandsIntermediate