Breadth-First Search
Breadth-first search explores a graph like ripples in a pond: first everything one step from the start, then everything two steps away, and so on. The tool that makes this order automatic is a queue - first in, first out. Because BFS visits nodes in order of distance, the first time it reaches any node is guaranteed to be along a fewest-steps path. That one guarantee makes it the answer to every "minimum number of moves" problem.
Watch the ripples spread
Press play - or drag the timeline and step through it yourself.Explore the graph from A, ring by ring: first everything 1 hop away, then 2 hops, then 3. A queue makes that order automatic.
Complexity
Rumor spreading through friends
You tell a secret to your friends (ring 1). The next day, each of them tells their friends (ring 2). The day after, those tell theirs (ring 3). The rumor reaches every person on the earliest possible day - it cannot arrive late, because every possible chain of friends is being followed at once, one day per hop. BFS is exactly this: the queue holds "people who just heard it and will spread it tomorrow".
How it works, step by step
Put the start node in a queue and mark it as seen, with distance 0.
Take the front node out of the queue.
Look at each of its neighbours. Every neighbour not seen before gets distance = current + 1, is marked seen, and joins the back of the queue.
A neighbour already seen is skipped - reaching it again cannot be shorter.
Repeat until the queue is empty. Nodes leave the queue in distance order: all the 1s, then the 2s, then the 3s...
Every node and edge is handled once - O(V + E) total.
The code, in JavaScript
BFS over an adjacency list. The `visited` set and the queue are the whole machine - everything else is bookkeeping.
function bfs(graph, start) {
const visited = new Set([start]);
const queue = [start];
const order = [];
while (queue.length > 0) {
const node = queue.shift(); // FRONT of the queue - that's BFS
order.push(node);
for (const next of graph[node] ?? []) {
if (!visited.has(next)) {
visited.add(next); // mark when ENQUEUING, not later
queue.push(next);
}
}
}
return order;
}
const graph = {
A: ["B", "C", "H"], B: ["A", "D"], C: ["A", "D", "E"],
H: ["A", "E"], D: ["B", "C", "G", "F"], E: ["C", "H", "F"],
F: ["D", "E"], G: ["D"],
};
bfs(graph, "A"); // → ["A", "B", "C", "H", "D", "E", "G", "F"]Dry run: BFS from node A
The same steps the visualizer plays, written as a table. Generated by running the real algorithm - not written by hand.
| Step | Ring | What happened |
|---|---|---|
| 1 | 1 | Take A out of the queue (distance 0). Look at its neighbours. |
| 2 | 2 | Take B out of the queue (distance 1). Look at its neighbours. |
| 3 | 2 | Take C out of the queue (distance 1). Look at its neighbours. |
| 4 | 2 | Take H out of the queue (distance 1). Look at its neighbours. |
| 5 | 3 | Take D out of the queue (distance 2). Look at its neighbours. |
| 6 | 3 | Take E out of the queue (distance 2). Look at its neighbours. |
| 7 | 4 | Take G out of the queue (distance 3). Look at its neighbours. |
| 8 | 4 | Take F out of the queue (distance 3). Look at its neighbours. |
Good choice when…
- You need the fewest steps/moves/hops and every step costs the same - BFS's first arrival is provably optimal.
- You want everything within k steps of a point (friends-of-friends, blast radius, n-move lookahead).
- Level-by-level processing matters: web crawling by depth, dependency layers, flood fill.
- The graph is implicit - puzzle states, grid cells, word ladders - and you generate neighbours on the fly.
Bad choice when…
- Edges have different costs - fewest hops is no longer cheapest; that is Dijkstra's job.
- The graph is gigantic and you only need *a* path, not the shortest - DFS uses far less memory than BFS's wide frontier.
- You need to detect cycles in a directed graph or explore "as deep as possible" - DFS's structure fits those questions.
Common mistakes
- Mark nodes as seen when you enqueue them, not when you dequeue. Marking late lets the same node enter the queue many times - on dense graphs that quietly turns O(V+E) into O(V²) or worse.
- `array.shift()` is O(n) in JavaScript - using it makes BFS itself O(V²) on big graphs. Use an index pointer (`queue[head++]`) or process ring-by-ring with two arrays.
- BFS's shortest-path guarantee dies the moment edges have weights. A 2-hop path of cost 1+1 beats a 1-hop edge of cost 10, but BFS picks the 1-hop. Weighted graphs need Dijkstra.
- The frontier can hold an entire level at once - on a wide graph (or a 1000×1000 grid) that is real memory, and it is the practical reason DFS sometimes wins despite BFS being "smarter".
Breadth-First Search vs. its closest relatives
| Algorithm | Best | Average | Worst | Space | Stable |
|---|---|---|---|---|---|
| Breadth-First Searchthis page | O(V + E) | O(V + E) | O(V + E) | O(V) | - |
| Depth-First Search | O(V + E) | O(V + E) | O(V + E) | O(V) | - |
| Dijkstra's Algorithm | O(E log V) | O(E log V) | O(E log V) | O(V) | - |
| Level-Order Traversal | O(n) | O(n) | O(n) | O(n) | - |