Breadth-First Search
Imagine you are mapping the shortest number of hops from a single social-media user to every other user in the network. You cannot afford to explore a distant friend-of-a-friend-of-a-friend before accounting for every direct friend. You need an exploration strategy that expands outward in perfect concentric rings. It must never skip ahead. That is exactly the guarantee Breadth-First Search (BFS) provides. It visits every vertex at distance from the source before visiting any vertex at distance . BFS is one of the two foundational traversal algorithms for graphs. The other is depth-first search. It underlies an enormous fraction of practical graph computation. Web crawlers use BFS-like layering to prioritize nearby pages. Network routers use it conceptually to compute hop-count distances. Puzzle solvers use it to find the fewest moves between two configurations. Social networks use it to compute "degrees of separation." Its defining feature is level-by-level expansion via a First-In-First-Out (FIFO) queue. This makes it the natural algorithm whenever "fewest edges" is the notion of distance that matters. This article builds BFS from first principles on top of Graph Representations: Adjacency Lists and Matrices. We assume that article as known machinery for storing and iterating over a graph's edges. We introduce the classic white/gray/black coloring scheme to reason precisely about correctness. We construct the BFS tree that records shortest-path structure. We then prove that BFS runs in linear time in the size of the graph. By the end, you will be able to trace BFS by hand on a small graph. You will state and justify its running time. You will understand exactly why the FIFO queue enforces the layered visiting order. You will also see how this single primitive sets up two major applications. These are BFS Shortest Paths on Unweighted Graphs and bipartite-graph algorithms built on augmenting paths.
- BFS explores a graph in layers, visiting all vertices at distance from the source before any vertex at distance .
- A FIFO queue is the mechanism that enforces this layered order — it is the single most important implementation detail.
- Every vertex is colored white (undiscovered), gray (discovered, frontier), or black (fully processed); this invariant is the key to the correctness proof.
- BFS runs in time and space, because every vertex is enqueued once and every edge is examined at most twice (once per representation).
- The predecessor pointers set during BFS form a BFS tree whose root-to-vertex paths are shortest paths in an unweighted graph.
- BFS is the direct ancestor of shortest-path-in-unweighted-graphs algorithms and of augmenting-path methods for bipartite matching.
BFS uses a FIFO queue to guarantee that vertices are discovered in strictly non-decreasing order of their distance from the source, producing shortest paths in unweighted graphs "for free."
The idea
A traversal of a graph is a systematic way of visiting every vertex reachable from a chosen source, following edges. Breadth-first search is a traversal strategy that explores the graph in layers (also called levels): layer contains only the source ; layer contains every vertex adjacent to that has not already been placed in a lower layer; layer contains every unvisited vertex adjacent to some vertex in layer ; and so on. Formally, layer consists of exactly the vertices whose shortest-path distance (in number of edges) from equals . The mechanism that produces this layering is a queue with First-In-First-Out (FIFO) discipline. When BFS discovers a vertex, it appends that vertex to the back of the queue; when BFS is ready to explore further, it removes a vertex from the front of the queue. Because vertices are removed in the same order they were added, and because every vertex adjacent to the vertex at the front of layer 's scan gets added while layer is still being processed, all of layer 's vertices are dequeued — and hence all of layer 's vertices are discovered — before any vertex of layer is dequeued. This ordering is the entire reason BFS behaves correctly; if you replaced the queue with a stack (LIFO), you would get depth-first search instead, with a completely different (non-layered) visiting order. To reason about BFS precisely, CLRS-style presentations track a color for each vertex during the search:
- White: undiscovered — the algorithm has not yet encountered this vertex.
- Gray: discovered but not finished — the vertex is in the queue (the "frontier"), and some of its neighbors may still be white.
- Black: finished — the vertex has been dequeued and all of its neighbors have been examined.
The gray vertices at any moment form a "wavefront" separating the black interior (fully explored) from the white exterior (unexplored). Each vertex also receives a distance label , the number of edges on the shortest path from , and a predecessor pointer , the vertex from which was first discovered. The collection of predecessor pointers defines the BFS tree, rooted at , whose tree edges are exactly the edges along which vertices were first discovered.
How it works
BFS begins by initializing every vertex to white, with distance and no predecessor, except the source, which is colored gray, given distance , and enqueued. The algorithm then repeatedly dequeues the front vertex , scans all of 's neighbors, and for every neighbor that is still white, discovers it: colors it gray, sets its distance to , sets its predecessor to , and enqueues it. Once all neighbors of have been scanned, is colored black. The process ends when the queue is empty, at which point every vertex reachable from has been assigned a correct distance and predecessor. ``` BFS(Graph G, Vertex s): for each vertex v in G.V: color[v] = WHITE dist[v] = INFINITY pred[v] = NIL color[s] = GRAY dist[s] = 0 pred[s] = NIL Q = empty FIFO queue ENQUEUE(Q, s) while Q is not empty: u = DEQUEUE(Q) for each v in Adj[u]: // scan neighbors via adjacency list if color[v] == WHITE: color[v] = GRAY dist[v] = dist[u] + 1 pred[v] = u ENQUEUE(Q, v) color[u] = BLACK // u is fully processed return dist[], pred[] // dist[]=INFINITY means unreachable ``` Two design points deserve emphasis. First, the coloring test `color[v] == WHITE` is what prevents a vertex from being enqueued twice; without it the algorithm could loop forever on a graph with cycles, or at least do redundant work. Second, the check happens at discovery time (when scanning 's neighbor list), not at dequeue time — this is precisely what makes the distance and predecessor values correct, since the first vertex to discover is guaranteed to be at the smallest possible distance from (this is proved below). Reconstructing the shortest path from to any reachable vertex is a simple walk backward along predecessor pointers: ``` PRINT-PATH(pred[], s, t): if t == s: print s else if pred[t] == NIL: print "no path from s to t exists" else: PRINT-PATH(pred[], s, pred[t]) print t ```
Complexity & correctness
Time complexity. BFS runs in time, where is the number of vertices and the number of edges. Justification: the initialization loop touches every vertex once, costing . Every vertex is enqueued at most once — the white-check guarantees this — so the main loop body (a dequeue) executes at most times. When a vertex is dequeued, the algorithm scans `Adj[u]`, whose total length summed over all vertices is for a directed graph and for an undirected graph (each edge appears in two adjacency lists). Since each adjacency-list entry is examined exactly once over the whole run, the total work spent scanning neighbors is . Adding the two contributions gives , which is linear in the size of the graph's standard representation. If the graph is instead given as an adjacency matrix, scanning neighbors of a single vertex costs regardless of actual degree, and the total time becomes — a good reminder that BFS's linear-time guarantee depends on using Graph Representations: Adjacency Lists and Matrices with adjacency lists, not matrices.
Space complexity. BFS uses space: the color, distance, and predecessor arrays are each of size , and the queue holds at most vertices at any time (it can never contain a vertex twice, by the white-check invariant).
Correctness — the coloring invariant. The proof rests on a loop invariant maintained by the queue's FIFO property. At any point during execution, order the vertices currently in the queue as from front to back. The invariant is:
In words: distances in the queue are non-decreasing, and the spread between the smallest and largest distance currently queued is at most . This invariant holds initially (only , with , is queued). Suppose it holds just before dequeuing . When is processed, every newly discovered neighbor is assigned and appended to the back of the queue. Since by the invariant, and , appending preserves the non-decreasing order and the spread-at-most-one property for the new queue . Because the invariant is preserved by every iteration and BFS never enqueues an already-colored (gray or black) vertex, each vertex's distance is set exactly once, at the moment it is first discovered — and it is discovered by a vertex whose own distance is the smallest possible, by the invariant. An induction on then shows computed by BFS equals , the true shortest-path distance (in edges) from to : the base case holds trivially, and if every vertex at distance is correct, any vertex at true distance has a neighbor at true distance ; is dequeued before any vertex at distance (by the queue invariant), so discovers (or some other equal-or-earlier vertex does), forcing ; and cannot be smaller than because a smaller BFS distance would imply a shorter true path, a contradiction. Hence exactly.
Correctness — the BFS tree. Because every non-source vertex receives only when is the (a) vertex that first discovers , and , following predecessor pointers from any reachable back to traces a path of length exactly — necessarily a shortest path. The set of all such tree edges forms the BFS tree, a spanning tree of the vertices reachable from ; edges of the original graph not used in this tree are either "cross edges" between vertices at the same or adjacent layers, but never edges that would shortcut more than one layer, since a would-be shortcut is contradicted by the distance argument above.
Honest tradeoffs. BFS finds shortest paths measured in edge count, not in weighted cost; on a weighted graph it gives no guarantee whatsoever, and one needs Dijkstra's algorithm or Bellman–Ford instead. BFS also uses auxiliary space for the queue and bookkeeping arrays, which can matter on graphs with billions of vertices, whereas depth-first search's implicit recursion stack sometimes affords different memory tradeoffs (though DFS does not give shortest paths). Finally, plain BFS explores the entire reachable component even if you only care about one destination; if you know a good heuristic estimate of distance to a specific target, informed search (A\*) can be faster in practice by exploring far fewer vertices.
Worked example
Consider the small undirected graph in the figure above, with source , and edges –, –, –, –, –, –. Assume adjacency lists list neighbors in alphabetical order. We trace BFS step by step.
| Step | Dequeue | Action | Queue after step | Colors updated |
|---|---|---|---|---|
| init | — | color all WHITE except s=GRAY, d[s]=0 | [s] | s: gray |
| 1 | s | scan a (white → gray, d=1, π=s), scan b (white → gray, d=1, π=s); color s BLACK | [a, b] | a,b: gray; s: black |
| 2 | a | scan c (white → gray, d=2, π=a), scan d (white → gray, d=2, π=a); color a BLACK | [b, c, d] | c,d: gray; a: black |
| 3 | b | scan d (already gray → skip), scan e (white → gray, d=2, π=b); color b BLACK | [c, d, e] | e: gray; b: black |
| 4 | c | no unvisited neighbors; color c BLACK | [d, e] | c: black |
| 5 | d | no unvisited neighbors; color d BLACK | [e] | d: black |
| 6 | e | no unvisited neighbors; color e BLACK | [ ] | e: black |
The algorithm terminates with distances , , , matching exactly the three layers in the diagram. Notice the edge –: when is dequeued at step 3, is already gray (discovered earlier by ), so this edge becomes a cross edge in the BFS tree rather than a tree edge — it connects two vertices in the same layer's boundary but does not change any distance, exactly as the invariant predicts (it links vertices whose distances differ by at most one). The BFS tree recorded by is: , , , , , matching the right panel of the figure. To find the shortest path from to , we walk , , giving the path of length , which indeed equals .
Common pitfalls & traps
| Pitfall | Why it breaks BFS | Fix |
|---|---|---|
| Marking a vertex "visited" at dequeue time instead of discovery time | Same vertex can be enqueued multiple times, inflating the queue and sometimes corrupting distances | Set color to GRAY and enqueue in the same step you first see the vertex |
| Using a stack instead of a queue | Produces depth-first order, destroys the layered guarantee and shortest-path correctness | Use a strict FIFO queue; this is the one non-negotiable ingredient |
| Forgetting to handle disconnected graphs | Vertices unreachable from are silently left with ; some implementations mistake this for an error | Explicitly treat as "unreachable," and run BFS again from a new source if full coverage of all components is required |
| Off-by-one in distance assignment | Assigning instead of , or updating distance after enqueue instead of before | Always set at the moment of discovery, before enqueueing |
| Assuming BFS gives shortest paths on weighted graphs | BFS counts edges only; a path with fewer edges can have far larger total weight | Use Dijkstra's algorithm or Bellman–Ford for weighted shortest paths |
| Re-coloring an already-black vertex when re-examining old edges | Wastes time and can, in buggy implementations, re-enqueue processed vertices | Always guard neighbor exploration with a strict `color == WHITE` check |
Pioneers & history
Breadth-first search's layered exploration idea predates modern computing; it appears implicitly in 19th-century work on maze-solving and graph connectivity, but its formalization as an algorithm for computers is tied closely to the development of graph theory and shortest-path computation in the mid-20th century. Edward F. Moore described a breadth-first procedure for finding paths through mazes in 1959, and Konrad Zuse had independently proposed a similar breadth-first solution for maze problems even earlier, though it remained largely unpublished at the time. The technique was subsequently absorbed into the broader toolkit of graph algorithms developed alongside network flow and shortest-path theory in the 1950s and 1960s, and formalized with the white/gray/black coloring scheme and rigorous complexity analysis in the algorithms-textbook tradition that crystallized in the late 20th century.
Origins · 1 minEdward F. Moore and how this was discovered
Moore worked at Bell Labs, where he contributed foundational results in switching theory and finite automata (Moore machines are named for him). His 1959 paper on finding shortest paths through mazes gave one of the earliest explicit descriptions of what we now call breadth-first search, framing it as a wavefront that spreads outward from the start cell one step at a time until it reaches the goal — precisely the layer-by-layer structure formalized later with queues and coloring invariants.
Where this matters
BFS's fingerprints are all over modern computing infrastructure. Web crawlers use BFS-like frontier expansion to prioritize discovering pages close to seed URLs before wandering deep into distant subgraphs of the web. Peer-to-peer and content-distribution networks use hop-count computations, conceptually BFS, to estimate proximity between nodes. Social networks compute "friends of friends" and shortest connection paths (the basis of "degrees of separation" features) using BFS from a query vertex. Puzzle-solving and game AI use BFS to find minimum-move solutions when all moves have equal cost — the 15-puzzle, Rubik's-Cube-adjacent search spaces, and pathfinding on unweighted grids all lean on it. In compilers and static analysis, BFS-style level-order traversal is used to process dependency graphs layer by layer. Networking protocols that build spanning trees for broadcast (such as certain multicast tree constructions) rely on the same layered-discovery principle. And BFS is the direct computational engine behind two important follow-on ideas: BFS Shortest Paths on Unweighted Graphs, which packages exactly the distance-and-predecessor machinery described here into a shortest-path subroutine, and augmenting-path methods for Bipartite Matching via Augmenting Paths, where repeated BFS-style searches for augmenting paths are the core loop of algorithms like Hopcroft–Karp.
Ask the tutor: How would you modify this BFS pseudocode to compute shortest paths from a set of multiple sources simultaneously, rather than a single vertex?
Does BFS work on directed graphs?
Yes. BFS makes no assumption that edges are undirected; it simply follows the outgoing edges listed in each vertex's adjacency list. On a directed graph, the resulting BFS tree captures shortest directed paths from the source, and some vertices may be unreachable even though they have an edge pointing into them, because BFS only ever traverses edges in their stored direction. The queue-invariant proof of correctness goes through unchanged, since it never relies on edges being symmetric — it only relies on the order in which vertices are discovered and enqueued.
Can BFS detect cycles in a graph?
BFS can detect cycles in an undirected graph: if, while scanning neighbors of , you encounter a gray or black neighbor that is not , that edge is a non-tree edge indicating a cycle. In directed graphs the situation is subtler, because BFS's coloring does not distinguish "back edges" the way DFS's discovery/finish-time stamps do; depth-first search is the more standard tool for directed-cycle detection, since its recursion stack directly exposes back edges pointing to an ancestor still on the stack.
How does BFS relate to Dijkstra's algorithm?
BFS is the special case of Dijkstra's algorithm where every edge has weight . Dijkstra's algorithm generalizes the FIFO queue to a priority queue keyed by tentative distance, always expanding the globally closest undiscovered vertex next. When all weights are equal, a priority queue keyed by distance behaves exactly like a FIFO queue, because ties are broken in arrival order and every newly discovered vertex's key is exactly one more than its discoverer's — so Dijkstra's algorithm collapses to BFS in that setting, but costs with a binary heap instead of BFS's .
What is the BFS tree, and is it unique?
The BFS tree is the tree formed by predecessor pointers set during the run: an edge for every reachable non-source vertex . It is a shortest-path tree, meaning the tree path from to any has length . It is generally not unique: if a vertex has multiple neighbors at the same, smaller distance, whichever one happens to dequeue first (determined by adjacency-list ordering or tie-breaking rules) becomes its parent. Different valid orderings of neighbor scans can produce different BFS trees, all equally correct as shortest-path trees.
Why does BFS need O(V) extra space even though the graph itself might already be stored?
BFS needs to record, for every vertex, three pieces of bookkeeping beyond the graph's own storage: its color (or simply a visited bit), its distance from the source, and its predecessor pointer. These arrays each have size , and the queue itself can hold up to vertices simultaneously in the worst case (for instance, a star graph where all leaves are discovered at once from the center). This overhead is unavoidable if you want to actually reconstruct paths and distances rather than merely test reachability, though a reachability-only version can sometimes trade away the predecessor array.
Does the order in which neighbors are scanned affect BFS's distance computation?
No — the distance result is always correct and unique regardless of neighbor-scan order, because the coloring-invariant proof never depends on which neighbor is examined first, only on the fact that all same-layer vertices are enqueued before any next-layer vertex. What can change with scan order is the specific BFS tree produced (which parent a multiply-reachable vertex gets) and the exact sequence of dequeues, but never the values themselves.
What is the difference between BFS and iterative deepening or bidirectional search?
Iterative deepening repeatedly runs depth-limited depth-first search with an increasing depth bound, which achieves BFS's layer-order guarantee while using only memory instead of BFS's , at the cost of redoing shallow work at every iteration. Bidirectional search runs two simultaneous BFS explorations, one from the source and one from the target, and stops when their frontiers meet; this can reduce the number of vertices explored from roughly to roughly on graphs with branching factor , a substantial practical speedup when both endpoints are known in advance.