BFS finds shortest paths

Slow and steady: BFS finds shortest paths

Here's a fact worth its own lesson: in an unweighted graph, breadth-first search finds the shortest path (fewest edges) from the start to every other reachable node.

Why it works

BFS explores in rings of increasing distance. It visits everything one edge away, then everything two edges away, then three, and so on. The first time it reaches a node, it has done so by the fewest possible edges — there's no shorter way, because every shorter distance was fully explored first.

Depth-first search gives you a path, but not necessarily the shortest — it plunges down one branch and might reach a node by a long, winding route before a short one. For "fewest steps" questions, reach for BFS.

Tracking distance

Carry the distance alongside each node in the queue, starting at 0 for the source:

queue = [(start, 0)]           // (node, dist) pairs
visited = { start: true }
while queue is not empty:
    node, dist = queue.removeFirst()
    if node == target:
        return dist             // first arrival = shortest
    for each next in graph[node]:
        if not visited[next]:
            visited[next] = true
            queue.append((next, dist + 1))

The same trick works on grids — distance is the number of cell-steps.

Grids are graphs too

A 2D grid is just a graph in disguise: each cell (r, c) is a node, and its neighbours are the adjacent cells. 4-directionally (up, down, left, right — no diagonals) the neighbours of (r, c) are:

(r-1, c)   (r+1, c)   (r, c-1)   (r, c+1)

that is, (r±1, c) and (r, c±1). A common way to generate them in code:

dirs = [(-1, 0), (1, 0), (0, -1), (0, 1)]
for each (dr, dc) in dirs:
    nr, nc = r + dr, c + dc
    // skip if out of bounds or already visited

"Slow and steady" — ring by ring — is exactly why BFS gives the shortest answer. The next problem puts it to work.