Breadth-First Search (BFS) in Python
Learn Breadth-First Search (BFS) in Python! A beginner-friendly, fun guide to exploring graphs level by level.
Try it yourself
Run this code directly in your browser. Click "Open in full editor" to experiment further.
Click Run to see output
Or press Ctrl + Enter
How it works
Breadth-first search (BFS) is the algorithm you reach for when someone says "shortest path" in an unweighted graph. Drop a pebble in a pond โ the ripple expands outward. BFS is that ripple, in code. ๐
Why is it the go-to for shortest path? Because BFS visits nodes strictly in order of distance from the start: distance-1 neighbors first, then distance-2, then distance-3. The moment you bump into your target, you are guaranteed it took the fewest possible edges to get there.
How BFS Works
BFS is a level-by-level graph traversal python programmers use everywhere. You keep a queue of nodes to visit and a visited set. Pop the front, push unseen neighbors to the back, repeat until empty.
Let's walk a tiny graph:
A
/ \
B C
/ / \
D E FStarting at A:
1. Queue: [A]. Pop A, enqueue B, C.
2. Queue: [B, C]. Pop B, enqueue D.
3. Queue: [C, D]. Pop C, enqueue E, F.
4. Pop D, E, F. Done.
Visit order: A โ B โ C โ D โ E โ F. Every node at depth k is fully processed before any node at depth k+1 โ that's the level guarantee.
Why a Queue (and not a stack)
Here's the elegant part: *the data structure is the algorithm*. Swap the queue for a stack and BFS becomes DFS โ same code, completely different traversal.
BFS spreads, DFS plunges. The container picks the personality.
Implementation Notes
Always use collections.deque. It's not a stylistic preference โ it's a complexity bug if you don't.
deque.popleft() is O(1) because a deque is a doubly-linked block structure with cheap access at both ends.list.pop(0) is O(n) because Python lists are contiguous arrays; removing index 0 forces every other element to shift one slot left. On a graph with 100k nodes this turns BFS from milliseconds into minutes.The deque python import you want is exactly:
from collections import deque
queue = deque([start])
queue.append(node) # O(1) at the back
queue.popleft() # O(1) at the frontThe visited set. Mark a node visited the moment you enqueue it, not when you pop it. If you wait until popping, the same node can be enqueued many times by different neighbors before it's processed โ duplicates explode and correctness suffers.
Tracking parents for path reconstruction. Carrying the full path inside the queue (like the snippet above) is readable but copies a list per enqueue. The scalable pattern is a parent dictionary: when you discover v from u, store parent[v] = u. After BFS, walk parents from target back to start and reverse the list.
Complexity Analysis
| Resource | Cost | Why |
|---|---|---|
| Time | O(V + E) | Every vertex enqueued once, every edge inspected once |
| Space | O(V) | Queue + visited set, both bounded by vertex count |
The representation matters too. With an adjacency list, finding a node's neighbors is O(text{deg}(v)), so the total scan is O(V + E). With an adjacency matrix, finding neighbors is always O(V) per node, pushing BFS to O(V^2) โ fine for dense graphs, wasteful for sparse ones. Most real-world graphs (social, web, road networks) are sparse, so adjacency lists win.
BFS vs DFS
| Property | BFS | DFS |
|---|---|---|
| Shortest path (unweighted) | Yes, guaranteed | No |
| Data structure | Queue | Stack (or recursion) |
| Space (worst case) | O(V), can be wide | O(h), depth of tree |
| Implementation | Iterative with deque | Recursive or iterative |
| Infinite graphs | Safe โ finds shallow goals | Can dive forever |
| Best for | Levels, shortest hops, broadcasts | Topological sort, cycle detection, backtracking |
Common Pitfalls
1. Marking visited too late. Mark nodes visited at enqueue time, not dequeue. Otherwise you'll enqueue duplicates and blow up runtime.
2. Using a list as a queue. list.pop(0) is O(n). Always use deque.popleft().
3. Reconstructing paths wrong. Don't store the full path in every queue item if your graph is large โ use a parent dict and reverse-walk at the end.
4. Assuming BFS works on weighted graphs. Shortest path unweighted only. The moment edges have weights โ 1, switch to Dijkstra or 0-1 BFS.
5. No `visited` set on cyclic graphs. Cycles will trap BFS in an infinite loop. The visited set is non-negotiable, even if you "know" the graph is a tree.
6. Forgetting directed vs undirected. In an undirected graph add both uโv and vโu. Forgetting the reverse edge silently breaks reachability.
Real-World Uses
BFS Variants
appendleft for weight-0 edges, append for weight-1. Solves shortest path in O(V+E) instead of Dijkstra's O(E log V).Frequently Asked Questions
Does BFS work on directed graphs? Yes. The algorithm is identical โ only follow edges in their declared direction. Reachability becomes one-way, which is usually what you want.
Can BFS handle weighted graphs? Not for shortest path. BFS counts edges, not weights. Use Dijkstra for non-negative weights, Bellman-Ford for negative weights, or 0-1 BFS for the special 0/1 case.
Why is `deque.popleft()` $O(1)$? A deque is implemented as a doubly-linked list of fixed-size blocks. Removing from either end just adjusts a pointer โ no element shifting, no reallocation.
How do I find the actual shortest path, not just the length? Maintain parent[child] = parent_node during BFS, then walk parents backward from target to start and reverse the list.
What's the difference between BFS and Dijkstra? BFS treats every edge as cost 1; Dijkstra uses a priority queue ordered by accumulated weight. On unweighted graphs they return the same answer, but BFS is faster and simpler. Add weights and you need Dijkstra.
Can BFS detect cycles? In undirected graphs, yes โ if you reach an already-visited node that isn't your immediate parent, you've found a cycle. For directed graphs, DFS with recursion stack tracking is cleaner.
Master the queue, the visited set, and the parent map, and you've unlocked a huge chunk of graph problems. ๐
Related examples
Binary Tree in Python
Learn Binary Trees in Python! A fun, beginner-friendly guide to building trees, inserting nodes, and exploring inorder, preorder, and postorder traversals.
Depth-First Search (DFS) in Python
Learn Depth-First Search (DFS) in Python! A super fun guide to exploring graphs, escaping mazes, and backtracking with recursive and iterative code.