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 kk from the source before visiting any vertex at distance k+1k+1. 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.

Key takeaways
  • BFS explores a graph in layers, visiting all vertices at distance dd from the source before any vertex at distance d+1d+1.
  • 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 O(V+E)O(V+E) time and O(V)O(V) 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.
The core insight

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 00 contains only the source ss; layer 11 contains every vertex adjacent to ss that has not already been placed in a lower layer; layer 22 contains every unvisited vertex adjacent to some vertex in layer 11; and so on. Formally, layer kk consists of exactly the vertices whose shortest-path distance (in number of edges) from ss equals kk. 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 kk's scan gets added while layer kk is still being processed, all of layer kk's vertices are dequeued — and hence all of layer k+1k+1's vertices are discovered — before any vertex of layer k+1k+1 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 d[v]d[v], the number of edges on the shortest path from ss, and a predecessor pointer π[v]\pi[v], the vertex from which vv was first discovered. The collection of predecessor pointers defines the BFS tree, rooted at ss, 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 ∞\infty and no predecessor, except the source, which is colored gray, given distance 00, and enqueued. The algorithm then repeatedly dequeues the front vertex uu, scans all of uu's neighbors, and for every neighbor that is still white, discovers it: colors it gray, sets its distance to d[u]+1d[u]+1, sets its predecessor to uu, and enqueues it. Once all neighbors of uu have been scanned, uu is colored black. The process ends when the queue is empty, at which point every vertex reachable from ss 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 uu's neighbor list), not at dequeue time — this is precisely what makes the distance and predecessor values correct, since the first vertex to discover vv is guaranteed to be at the smallest possible distance from ss (this is proved below). Reconstructing the shortest path from ss to any reachable vertex tt 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 ```

Figure
BFS layering on a small graph: queue state and the resulting BFS tree
Graph, colored by layer L0 L1 L2 s a b c d e dark = layer 0, mid-gray = layer 1, light = layer 2 Resulting BFS tree (π pointers) s a b c d e note: edge b–d is a cross edge, absorbed by whichever endpoint discovers d first

Complexity & correctness

Time complexity. BFS runs in O(V+E)O(V+E) time, where VV is the number of vertices and EE the number of edges. Justification: the initialization loop touches every vertex once, costing O(V)O(V). Every vertex is enqueued at most once — the white-check guarantees this — so the main loop body (a dequeue) executes at most VV times. When a vertex uu is dequeued, the algorithm scans `Adj[u]`, whose total length summed over all vertices is Θ(E)\Theta(E) for a directed graph and Θ(2E)\Theta(2E) 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 O(E)O(E). Adding the two contributions gives O(V+E)O(V+E), 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 O(V)O(V) regardless of actual degree, and the total time becomes O(V2)O(V^2) — 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 O(V)O(V) space: the color, distance, and predecessor arrays are each of size VV, and the queue holds at most VV 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 v1,v2,…,vkv_1, v_2, \ldots, v_k from front to back. The invariant is:

d[v1]≤d[v2]≤⋯≤d[vk]≤d[v1]+1.d[v_1] \le d[v_2] \le \cdots \le d[v_k] \le d[v_1] + 1.

In words: distances in the queue are non-decreasing, and the spread between the smallest and largest distance currently queued is at most 11. This invariant holds initially (only ss, with d[s]=0d[s]=0, is queued). Suppose it holds just before dequeuing v1v_1. When v1v_1 is processed, every newly discovered neighbor ww is assigned d[w]=d[v1]+1d[w] = d[v_1] + 1 and appended to the back of the queue. Since d[v1]≤d[vk]d[v_1] \le d[v_k] by the invariant, and d[w]=d[v1]+1≤d[vk]+1d[w] = d[v_1]+1 \le d[v_k] + 1, appending ww preserves the non-decreasing order and the spread-at-most-one property for the new queue v2,…,vk,w,…v_2, \ldots, v_k, w, \ldots. 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 d[v]d[v] then shows d[v]d[v] computed by BFS equals δ(s,v)\delta(s,v), the true shortest-path distance (in edges) from ss to vv: the base case d[s]=0=δ(s,s)d[s]=0=\delta(s,s) holds trivially, and if every vertex at distance <k< k is correct, any vertex vv at true distance kk has a neighbor uu at true distance k−1k-1; uu is dequeued before any vertex at distance ≥k\ge k (by the queue invariant), so uu discovers vv (or some other equal-or-earlier vertex does), forcing d[v]≤kd[v] \le k; and d[v]d[v] cannot be smaller than kk because a smaller BFS distance would imply a shorter true path, a contradiction. Hence d[v]=k=δ(s,v)d[v] = k = \delta(s,v) exactly.

Correctness — the BFS tree. Because every non-source vertex vv receives π[v]=u\pi[v] = u only when uu is the (a) vertex that first discovers vv, and d[v]=d[u]+1d[v] = d[u]+1, following predecessor pointers from any reachable tt back to ss traces a path of length exactly d[t]=δ(s,t)d[t] = \delta(s,t) — necessarily a shortest path. The set of all such tree edges (π[v],v)(\pi[v], v) forms the BFS tree, a spanning tree of the vertices reachable from ss; 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 O(V)O(V) 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 ss, and edges ss–aa, ss–bb, aa–cc, aa–dd, bb–dd, bb–ee. Assume adjacency lists list neighbors in alphabetical order. We trace BFS step by step.

StepDequeueActionQueue after stepColors updated
init—color all WHITE except s=GRAY, d[s]=0[s]s: gray
1sscan a (white → gray, d=1, π=s), scan b (white → gray, d=1, π=s); color s BLACK[a, b]a,b: gray; s: black
2ascan c (white → gray, d=2, π=a), scan d (white → gray, d=2, π=a); color a BLACK[b, c, d]c,d: gray; a: black
3bscan d (already gray → skip), scan e (white → gray, d=2, π=b); color b BLACK[c, d, e]e: gray; b: black
4cno unvisited neighbors; color c BLACK[d, e]c: black
5dno unvisited neighbors; color d BLACK[e]d: black
6eno unvisited neighbors; color e BLACK[ ]e: black

The algorithm terminates with distances d[s]=0d[s]=0, d[a]=d[b]=1d[a]=d[b]=1, d[c]=d[d]=d[e]=2d[c]=d[d]=d[e]=2, matching exactly the three layers in the diagram. Notice the edge bb–dd: when bb is dequeued at step 3, dd is already gray (discovered earlier by aa), 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 π\pi is: s→as \to a, s→bs \to b, a→ca \to c, a→da \to d, b→eb \to e, matching the right panel of the figure. To find the shortest path from ss to ee, we walk π[e]=b\pi[e] = b, π[b]=s\pi[b] = s, giving the path s→b→es \to b \to e of length 22, which indeed equals d[e]d[e].

Common pitfalls & traps

PitfallWhy it breaks BFSFix
Marking a vertex "visited" at dequeue time instead of discovery timeSame vertex can be enqueued multiple times, inflating the queue and sometimes corrupting distancesSet color to GRAY and enqueue in the same step you first see the vertex
Using a stack instead of a queueProduces depth-first order, destroys the layered guarantee and shortest-path correctnessUse a strict FIFO queue; this is the one non-negotiable ingredient
Forgetting to handle disconnected graphsVertices unreachable from ss are silently left with d[v]=∞d[v]=\infty; some implementations mistake this for an errorExplicitly treat d[v]=∞d[v]=\infty as "unreachable," and run BFS again from a new source if full coverage of all components is required
Off-by-one in distance assignmentAssigning d[v]=d[u]d[v]=d[u] instead of d[u]+1d[u]+1, or updating distance after enqueue instead of beforeAlways set d[v]=d[u]+1d[v] = d[u]+1 at the moment of discovery, before enqueueing
Assuming BFS gives shortest paths on weighted graphsBFS counts edges only; a path with fewer edges can have far larger total weightUse Dijkstra's algorithm or Bellman–Ford for weighted shortest paths
Re-coloring an already-black vertex when re-examining old edgesWastes time and can, in buggy implementations, re-enqueue processed verticesAlways 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
EF
Edward F. Moore
1925–2003 · American computer scientist and mathematician, known for automata theory and maze-solving algorithms

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.

History
1945
Konrad Zuse informally describes a breadth-first-style approach to path problems in unpublished work.
1959
Edward Moore publishes an explicit breadth-first algorithm for solving mazes, establishing the wavefront framing of shortest-path search.
1961
C. Y. Lee applies the same wavefront idea to wire routing in circuit design, producing the Lee routing algorithm.
1972
Foundational work in graph algorithms (Tarjan and others) systematizes traversal-based methods including BFS and DFS with formal complexity analysis.
1990
The coloring-invariant presentation of BFS with O(V+E)O(V+E) analysis becomes the standard textbook treatment, cementing today's pedagogy.

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 uu, you encounter a gray or black neighbor vv that is not π[u]\pi[u], 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 11. 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 O((V+E)log⁡V)O((V+E)\log V) with a binary heap instead of BFS's O(V+E)O(V+E).

What is the BFS tree, and is it unique?

The BFS tree is the tree formed by predecessor pointers π[v]\pi[v] set during the run: an edge (π[v],v)(\pi[v], v) for every reachable non-source vertex vv. It is a shortest-path tree, meaning the tree path from ss to any vv has length δ(s,v)\delta(s,v). 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 VV, and the queue itself can hold up to VV vertices simultaneously in the worst case (for instance, a star graph where all leaves are discovered at once from the center). This O(V)O(V) 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 d[v]d[v] 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 O(depth)O(\text{depth}) memory instead of BFS's O(V)O(V), 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 bdb^d to roughly 2bd/22b^{d/2} on graphs with branching factor bb, a substantial practical speedup when both endpoints are known in advance.

Before this

Where this leads