BFS: The Wavefront

Tap the graph to expand the next wave
Ad=0BCDEFGH
d=0d=1d=2d=3

Watch how BFS expands from a source node. It never dives deep -- it spreads outward, one distance level at a time. Every node at distance 1 lights up before any node at distance 2. Every distance-2 node before any distance-3 node.

This wavefront behavior is not an accident of implementation. It is a direct consequence of the FIFO queue. When BFS discovers a neighbor, it places that neighbor at the back of the queue. Meanwhile, all the nodes at the current distance are already waiting at the front. So every distance-1 node gets processed before any distance-2 node even gets a chance. The queue acts as a sorting mechanism, naturally grouping nodes by their distance from the source without ever comparing distances explicitly.

Here is why this matters: the first time BFS reaches any node, it has taken the fewest possible edges to get there. There is no shorter path hiding somewhere that BFS has not tried yet, because BFS has already exhausted every path of length k before touching any path of length k+1. This is the core guarantee that makes BFS the tool for shortest paths on unweighted graphs.

The wavefront is not just a visualization trick. It is the algorithm's proof of correctness, animated.

Predict the frontier

BFS processes nodes level by level. The frontier is the set of nodes currently being explored -- all at the same distance from the source. When BFS finishes processing every node in the current frontier, it discovers the next layer: the unvisited neighbors of those frontier nodes.

This is a useful mental model to internalize. At any point during BFS, the queue holds at most two adjacent distance levels. The nodes being processed right now (the current frontier) and the nodes just discovered (the next frontier, waiting at the back of the queue). Once the current frontier is fully processed, the next frontier becomes the current one, and the cycle repeats.

Think of it like ripples in a pond. Drop a stone, and the first ring expands outward. It does not skip ahead to create ring three before ring two is complete. Each ring is a frontier, and BFS guarantees they expand in strict distance order.

The pulsing nodes below are the current frontier. Your job: tap the nodes BFS will discover next -- the unvisited neighbors of those pulsing nodes. If you can predict the next frontier correctly, you have the wavefront model in your head.

Level 1 of 3 — tap the next nodes BFS discovers
Ad=0BCDEFGH

Track the frontier

You just predicted a single frontier transition. Now try sustaining it across the entire traversal.

BFS processes nodes level by level. After processing a node, its unvisited neighbors join the next frontier. But here is the challenge: as the graph grows, keeping track of which nodes you have already visited and which belong to the next frontier gets genuinely difficult. Your working memory fills up fast.

This is exactly why BFS uses a queue -- to offload that tracking to a data structure. But before you lean on the queue, it is worth feeling the difficulty yourself. When you struggle to remember which nodes are visited and which are next, you are experiencing the problem the queue solves. That felt difficulty is the point.

Your job: be the computer. After each level, pick which nodes come next. No data structure to help you -- just your memory. Pay attention to which moments feel easy (sparse graphs with few neighbors) and which feel overwhelming (dense graphs where every node connects to many others). That difficulty gradient maps directly to the queue's workload.

ABCDEFGH

BFS just finished processing [A]. Which nodes are at distance 1?

Tap all that belong in the next frontier. No queue to help you.

Level 1 of 3

The race: DFS vs BFS

Same graph. Same start. Same target. Two different strategies racing to find the shortest path.

DFS dives deep, committing fully to one branch before backtracking. It might get lucky and stumble onto the target early -- or it might wander through the entire graph, exploring dead ends and long detours. Even when DFS finds the target, it has no way of knowing whether the path it took is the shortest. It found a path, not the path.

BFS expands level by level, methodically checking every node at distance 1, then distance 2, then distance 3. It seems slower -- more cautious, less aggressive. But the moment the target appears in the frontier, BFS can stop immediately. Why? Because every shorter path has already been explored. If the target were reachable in fewer steps, BFS would have found it in an earlier frontier.

This is the fundamental tradeoff. DFS is a gambler: sometimes fast, sometimes catastrophically slow, and never certain it has the shortest path. BFS is a methodical sweep: always finds the shortest path, always visits at most every node once, never wastes time on a longer path when a shorter one exists.

Start the race below and watch the strategies diverge. Pay attention to the total nodes visited by each -- BFS often visits fewer nodes, not more, because it stops the moment it finds the target.

Find node H starting from A
DFS
ABCDEFGH
nodes visited
BFS
ABCDEFGH
nodes visited

The distance invariant

Why does BFS guarantee shortest paths? The answer is a single invariant that holds throughout the entire traversal: the queue is always sorted by distance.

No higher-distance node ever sits ahead of a lower-distance node. Think about why. When you dequeue a node at distance d and enqueue its unvisited neighbors, those neighbors are at distance d+1. They go to the back of the queue. Meanwhile, the remaining distance-d nodes are still at the front. So the queue always contains a block of distance-d nodes followed by a block of distance-(d+1) nodes -- nothing else.

This monotonic ordering is what makes BFS a shortest-path algorithm. When you dequeue a node, you know its distance is the smallest possible, because every smaller distance has already been fully processed. No future dequeue will produce a shorter path to that node. First visit equals shortest distance.

This invariant also explains why BFS does not work on weighted graphs. If edge weights vary, a neighbor might be at distance d+3 rather than d+1, and it would land at the back of the queue behind a node at distance d+1 -- breaking the sorted order. For weighted graphs, you need a priority queue (Dijkstra's algorithm) to maintain the invariant.

Step through the queue below. At each step, predict the distance of the next node to be dequeued. Watch how the distance never decreases -- the invariant holds from the first dequeue to the last.

Step 1/8
ABCDEFGH
FrontQueueBack
?
?
B
d=1
C
d=1
What distance is the next node coming off the queue?

The cost of DFS shortest paths

“Can DFS find shortest paths too?” Technically, yes -- but the cost is brutal.

The problem is that DFS has no way to know whether the path it found is the shortest. It committed to one branch, reached the target, and recorded that path length. But what about the other branches? Any of them might contain a shorter path. To guarantee the shortest, DFS must explore every possible path from source to target and keep the minimum.

On a branching graph, the number of simple paths between two nodes can grow exponentially. A graph with V nodes and average branching factor b might have O(b^V) paths. DFS has to walk every single one to be sure it found the shortest. BFS, by contrast, visits each node exactly once -- O(V + E) work -- and stops the moment it reaches the target. It never backtracks, never revisits, never wonders “was there a shorter way?”

This is not a minor difference. On a grid with 100 nodes, BFS does ~100 operations. DFS trying to find the shortest path might do millions. The gap only widens as graphs grow.

Watch the operation counters below diverge in real time. Both algorithms find the same shortest path, but notice how many more operations DFS needs to prove it found the shortest.

Branching factor 3, depth 4 -- finding shortest path to target

DFS (all paths)

node visits

BFS (level by level)

node visits

This tree has 3 children per node, 4 levels deep. To find a target, how do DFS and BFS compare in node visits?

Spot the pattern

You now know the core rule: BFS guarantees shortest paths on unweighted edges. DFS guarantees reachability -- it can tell you whether a path exists, but not whether it is the shortest.

The telltale signs of a BFS shortest-path problem: the question asks for “minimum number of moves,” “fewest steps,” “shortest transformation sequence,” or “minimum distance.” All edges cost the same -- one step, one move, one hop. No edge is cheaper or more expensive than another.

The telltale signs of a DFS exploration problem: the question asks “does a path exist,” “find all connected components,” “detect a cycle,” or “enumerate all possibilities.” The answer is about existence or exhaustiveness, not about minimality.

There are edge cases. “Find the shortest path in a weighted graph” looks like BFS territory, but the unequal weights break the distance invariant -- you need Dijkstra. “Find the shortest path in a graph where every edge has weight 1” is BFS, even if the problem does not explicitly say “unweighted.” The weight uniformity is what matters, not the label.

For each problem below, decide: is this a BFS shortest-path problem, or a DFS exploration problem? Pay attention to the wording. The verb -- “minimum,” “all,” “any,” “fewest” -- is your strongest signal.

Problem 1/5

Minimum number of knight moves to reach a square on a chessboard

Build it from scratch

You have seen the wavefront, felt the frontier expand, watched BFS and DFS race, and understood the distance invariant. Now construct the BFS shortest-path template yourself.

There is a reason this matters beyond understanding. In an interview, you will not be asked to explain BFS -- you will be asked to write it, under time pressure, on a problem you have never seen before. The difference between “I understand the wavefront” and “I can produce correct BFS code from memory” is the difference between a good conversation and a passing score. Recognition is not the same as generation.

Three blanks span the critical decisions. The first is queue initialization: how do you seed BFS with the source node and its starting distance? This is the moment that determines whether BFS even starts correctly -- getting the initial state wrong means every subsequent distance is off by one. The second is the neighbor loop: when you process a node, how do you discover and enqueue its unvisited neighbors? This is where the wavefront actually expands, and it is also where the visited check must happen (mark-on-push, as you learned in the previous lesson). The third is the distance update: how do you record each neighbor's distance as exactly one more than the current node's?

These three decisions -- init, expand, update -- form the skeleton of every BFS shortest-path solution. Once you can fill them without hesitation, you can solve Word Ladder (LC 127), Shortest Path in Binary Matrix (LC 1091), Rotting Oranges (LC 994), and dozens of similar problems by swapping only the neighbor function. The skeleton stays constant; only the definition of “neighbor” changes.

function bfsShortestPath(graph, start, target) {
const queue = [];
const visited = new Set([start]);
while (queue.length > 0) {
const [node, dist] = queue.shift();
if (node === target) return dist;
{
if (!visited.has(n)) {
visited.add(n);
queue.push([n, ]);
}
}
}
return -1;
}

Refactor: DFS to BFS

Here is a function that finds a path using DFS. It works -- it reaches the target and returns a valid path. But on this particular graph, DFS wanders through 5 nodes when the shortest path is only 3 hops. The path is correct in the sense that it connects source to target. It is incorrect in the sense that it is not the shortest.

This scenario comes up constantly in real code reviews and interviews. Someone writes a perfectly functional DFS, tests it on small inputs, sees correct paths, and ships it. Then a test case with a large branching graph reveals the answer is wrong -- not crashing wrong, but suboptimal wrong. The function returns a path of length 7 when the shortest is 3. The code structure is clean, the logic is valid, and the bug is invisible unless you know that DFS does not guarantee shortest paths on unweighted graphs.

This is the exact scenario where BFS should replace DFS. The problem asks for minimum steps, not any path. DFS cannot guarantee minimality without exhaustive search (which, as you saw, costs exponentially more). The refactor is surgical: three structural changes, and the rest of the code stays untouched.

Your job: identify the depth-first pattern in the code -- the stack, the LIFO pop, the eager recursion -- and replace it with the breadth-first equivalent. Three blanks, each targeting one structural change: the data structure (stack to queue), the retrieval method (pop to dequeue), and the distance tracking (absent in DFS, essential in BFS).

The algorithm's shape changes, but the neighbor iteration stays the same. That is the insight: BFS and DFS share the same expansion logic. The only difference is the order in which they process discovered nodes. Once you internalize that the traversal skeleton is shared and only the frontier discipline differs, converting between the two becomes mechanical.

shortestPath.tsStep 1 / 2
function shortestPath(graph, start, end) {
  const visited = new Set()
  function dfs(node, path) {
    if (node === end) return path
    visited.add(node)
    for (const neighbor of graph[node]) {
      if (!visited.has(neighbor)) {
        const result = dfs(neighbor, [...path, neighbor])
        if (result) return result
      }
    }
    return null
  }
  return dfs(start, [start])
}
ABCDEgraph: A→[B,D] B→[C] C→[D] D→[E]   start=A end=E
path5nodes

DFS follows the first neighbor greedily. How many nodes will this path visit from A to E?

Final check

Three questions to lock in the core insight. BFS is not just a traversal algorithm -- it is a proof machine. The FIFO queue enforces distance ordering. The distance invariant guarantees that the first visit to any node is the shortest path. And the wavefront expansion means you never waste work exploring longer paths when shorter ones remain.

This is worth pausing on. Most people learn BFS as a procedure: “use a queue, mark visited, done.” But the procedure is just the mechanism. The reason BFS works for shortest paths is that the FIFO discipline preserves a monotonic distance ordering in the queue -- no node at distance d+1 ever gets processed before all nodes at distance d. That single property is what makes the first visit to any node automatically the shortest path. Break that property (by using a stack, or by allowing weighted edges), and the guarantee vanishes. The algorithm still runs, still visits every node, still terminates -- but the distances it computes are no longer correct.

These questions test whether you own the why, not just the how. You will need to reason about the distance invariant, identify when BFS is the wrong tool (weighted edges break the guarantee), and apply the wavefront model to a problem you have not seen before. If you can explain why BFS fails on weighted graphs -- not just that it does -- you have the invariant internalized.

Question 1/3

Why does BFS guarantee shortest path in unweighted graphs?