Breadth-first search in 4 minutes
Channel: Michael Sambol
Watch on YouTube →Algorithm • medium
BFS explores a graph layer by layer using a **queue**: visit a node, enqueue its unvisited neighbors, repeat until the queue is empty.
On an unweighted graph, the first time BFS reaches a node is along a shortest path. This is the single most important property of BFS.
It runs in O(V + E) time and O(V) space touching every vertex and every edge at most a constant number of times.
Typical Complexity Baseline
| Metric | Value |
|---|---|
| Time | O(V + E) |
| Space | O(V) |
See It
A small animated SVG that illustrates the core idea the moving parts you need to keep in your head when you read the rest of the guide.
All nodes at distance d are visited before any at distance d + 1.
Watch First
A quick visual walkthrough before the detailed sections pick the format that helps you learn fastest.
Channel: Michael Sambol
Watch on YouTube →Mechanics
The building blocks and terminology you need before everything else clicks. Skim or deep-read.
What it is Holds the frontier nodes discovered but not yet expanded. Use a deque for O(1) popleft.
Why it matters Queue (FIFO) is a required building block for understanding how Breadth-First Search (BFS) stays correct and performant on large inputs.
What it is Records which nodes have been enqueued so each is processed exactly once. Without it, cycles cause infinite loops.
Why it matters Visited set is a required building block for understanding how Breadth-First Search (BFS) stays correct and performant on large inputs.
What it is Optional bookkeeping: distance from source, parent pointers for path reconstruction.
Why it matters Distance / parent map is a required building block for understanding how Breadth-First Search (BFS) stays correct and performant on large inputs.
What it is Implicit or explicit marker between BFS layers useful when you need 'all nodes at depth k'.
Why it matters Level boundary is a required building block for understanding how Breadth-First Search (BFS) stays correct and performant on large inputs.
What it is The set of nodes currently in the queue the wavefront of exploration.
Why it matters The set of nodes currently in the queue the wavefront of exploration. In Breadth-First Search (BFS), this definition helps you reason about correctness and complexity when inputs scale.
What it is All nodes at the same distance from the source. BFS processes layers in order.
Why it matters All nodes at the same distance from the source. BFS processes layers in order. In Breadth-First Search (BFS), this definition helps you reason about correctness and complexity when inputs scale.
What it is Path with the fewest edges. BFS finds it; Dijkstra/A* are needed for weighted edges.
Why it matters Path with the fewest edges. BFS finds it; Dijkstra/A* are needed for weighted edges. In Breadth-First Search (BFS), this definition helps you reason about correctness and complexity when inputs scale.
What it is Variant using a deque push to front for weight-0 edges, back for weight-1. Solves shortest path on graphs with binary weights in O(V+E).
Why it matters Variant using a deque push to front for weight-0 edges, back for weight-1. Solves shortest path on graphs with binary weights in O(V+E). In Breadth-First Search (BFS), this definition helps you reason about correctness and complexity when inputs scale.
What it is Seed the queue with multiple starting nodes at distance 0. Useful for 'nearest of any X' problems.
Why it matters Seed the queue with multiple starting nodes at distance 0. Useful for 'nearest of any X' problems. In Breadth-First Search (BFS), this definition helps you reason about correctness and complexity when inputs scale.
Walkthrough
Every frame of the concept diagram, laid out top-to-bottom so you can scroll through the algorithm at your own pace.
Init. Mark S visited, queue = [S].
Dequeue S. Examine neighbors: A, B.
Enqueue A, B. Queue = [A, B].
Dequeue A. Examine neighbors: S, C, D.
Enqueue C, D. Queue = [B, C, D].
Dequeue B. Examine neighbors: S, D.
Dequeue C. Examine neighbors: A, E.
Enqueue E. Queue = [D, E].
Dequeue D. Examine neighbors: A, B, F.
Enqueue F. Queue = [E, F].
Dequeue E. Examine neighbors: C, F, G.
Enqueue G. Queue = [F, G].
Dequeue F. Examine neighbors: D, E, G.
Dequeue G. Examine neighbors: E, F, H, I.
Enqueue H, I. Queue = [H, I].
Dequeue H. Examine neighbors: G.
Dequeue I. Examine neighbors: G.
Queue empty → BFS done. Visited 10 nodes in layer order.
Tradeoffs
When this technique wins, when it loses, and what to reach for instead.
Choose this when Choose BFS when you need shortest path / minimum number of steps, or layer-order processing.
Tradeoff BFS uses O(V) queue memory in the worst case (wide graphs); DFS uses O(depth) stack memory (often less).
Choose this when Choose BFS when all edge weights are equal O(V+E) is faster than Dijkstra's O((V+E) log V).
Tradeoff BFS doesn't handle weighted edges. The moment weights differ, you need Dijkstra (or 0-1 BFS for binary weights).
Choose this when Choose plain BFS when you have no heuristic or the graph is small.
Tradeoff A* explores far fewer nodes when you have a good heuristic, but it's pointless without one.
In Production
Where this shows up at real companies and what the on-call engineer learned the hard way.
'Degrees of connection' (1st, 2nd, 3rd) is computed with BFS over the social graph, capped at depth 3 because the graph explodes combinatorially after that.
Takeaway BFS is the natural fit for 'how many hops away' but capping depth is essential at scale.
Crawlers BFS the web from seed URLs, prioritizing breadth-first to discover new domains before going deep into any one site.
Takeaway BFS gives breadth coverage; DFS would get stuck in a single domain's deep links.
Simple grid-based games use BFS for enemy pathfinding when the grid is unweighted. AAA titles upgrade to A* for weighted terrain or large maps.
Takeaway BFS is the right starting point; upgrade only when profiling shows you need to.
Code
A clean reference implementation in TypeScript, Python, and Go. Switch languages with the toggle.
Standard queue-based BFS that records the shortest distance from the source to every reachable node.
Complexity: Time O(V + E), Space O(V)
BFS with shortest-path distance tracking
function shortestDistancesByBfs(params: {
sourceNode: string
adjacencyByNode: Map<string, string[]>
}): Map<string, number> {
const distanceByNode = new Map<string, number>()
const explorationQueue: string[] = [params.sourceNode]
distanceByNode.set(params.sourceNode, 0)
while (explorationQueue.length > 0) {
const currentNode = explorationQueue.shift() as string
const currentDistance = distanceByNode.get(currentNode) as number
for (const neighborNode of params.adjacencyByNode.get(currentNode) ?? []) {
if (distanceByNode.has(neighborNode)) continue
distanceByNode.set(neighborNode, currentDistance + 1)
explorationQueue.push(neighborNode)
}
}
return distanceByNode
}
Watch Out
The bugs and edge cases that bite engineers most often. Skim before you ship.
list instead of a deque in Python list.pop(0) is O(n), making your BFS accidentally O(V²).Pro Tips
Small habits that pay off every time you reach for this pattern.
Pattern Recognition
Concrete signals that tell you this is the right pattern both at work and on LeetCode.
Practice
A curated easy-to-hard problem ladder. Each one reinforces a specific aspect of the pattern.
| # | Problem | Difficulty | Typical Complexity |
|---|---|---|---|
| 1 | Binary Tree Level Order Traversal | Medium | O(n) |
| 2 | Number of Islands | Medium | O(rows × cols) |
| 3 | Rotting Oranges | Medium | O(rows × cols), multi-source BFS |
| 4 | Shortest Path in Binary Matrix | Medium | O(n²) |
| 5 | 01 Matrix | Medium | O(rows × cols), multi-source BFS |
| 6 | Open the Lock | Medium | O(10⁴ × 4) |
| 7 | Word Ladder | Hard | O(n × wordLength²) |
| 8 | Bus Routes | Hard | O(n × m) |