Breadth-First Search
Explore level by level. The only algorithm that guarantees the shortest path in an unweighted graph. One queue, one visited set.
DFS finds a path — BFS finds the shortest path
The insight: a queue enforces level order. All nodes at distance 1 are processed before any node at distance 2. The first time you reach the target, you have taken the fewest possible steps — BFS cannot overshoot. Use collections.deque for O(1) popleft.
Two BFS Flavors
BFS on graphs vs BFS on trees — same algorithm, different structure.
Queue holds (node, distance) pairs. Visited set prevents revisits. First time target is dequeued, its distance is the answer.
Queue holds tree nodes. Level separation tracked by snapshotting queue size at start of each level. Each snapshot = one level.
The 3-Step BFS Framework
Three decisions before writing a single loop.
Use collections.deque, not a list. Append start node AND mark it visited before the while loop. Never mark visited inside the while loop — creates race conditions.
from collections import deque
visited = {start}
queue = deque([(start, 0)])For graph BFS: popleft, check neighbors, enqueue unvisited. For tree level-order: snapshot len(queue), loop exactly that many times to process one full level.
while queue:
node, dist = queue.popleft()
for nb in graph[node]:
if nb not in visited:
visited.add(nb)
queue.append((nb, dist+1))For shortest path: return distance the instant the target is dequeued (or when target is first enqueued as a neighbor). First reach = shortest distance. No need to continue.
for nb in graph[node]:
if nb == target:
return dist + 1 # first reach = shortest
if nb not in visited:
visited.add(nb)
queue.append((nb, dist+1))BFS vs DFS — Decision Guide
The most important graph algorithm decision you will make.