Breadth-First Search Algorithm- Complete Implementation Guide
What Is Breadth-First Search (BFS)?
Breadth-First Search is a graph traversal algorithm that explores all nodes at the current depth before moving to nodes at the next depth level. Think of it like ripples spreading outward from a stone dropped in water.
You start at a source node, visit all its neighbors, then visit all their unvisited neighbors, and so on. The queue is the secret weapon here—it keeps track of what comes next.
When BFS Actually Makes Sense
Use BFS when you need to find the shortest path in an unweighted graph. That's the main use case, and it's a damn good one.
Other scenarios where BFS wins:
- Level-order tree traversal
- Finding connected components
- Checking if a graph is bipartite
- Social network friend suggestions
- GPS navigation nearest routes
How BFS Works: The Core Logic
Here's the step-by-step breakdown:
- Start by putting the source node in a queue
- Mark it as visited
- While the queue isn't empty, pop the front node
- Process that node
- Push all unvisited neighbors into the queue
- Mark them as visited
- Repeat until queue is empty
The visited set prevents infinite loops in graphs with cycles. Skip this step and you'll loop forever. Don't skip it.
BFS vs Depth-First Search: The Real Differences
Most people get confused here. Here's the truth:
| Aspect | BFS | DFS |
|---|---|---|
| Data structure | Queue (FIFO) | Stack (LIFO) |
| Traversal pattern | Level by level | Deep first, backtrack |
| Shortest path | Guaranteed | Not guaranteed |
| Memory usage | Wider trees eat memory | Deeper trees eat memory |
| Best for | Shortest paths, levels | Cycles, paths, connectivity |
Pick BFS for shortest path. Pick DFS for memory-efficient deep exploration or when you just need to visit everything.
Implementation in Python
Here's a clean, working BFS implementation you can copy-paste:
from collections import deque
def bfs(graph, start):
visited = set()
queue = deque([start])
visited.add(start)
while queue:
node = queue.popleft()
print(node, end=' ')
for neighbor in graph[node]:
if neighbor not in visited:
visited.add(neighbor)
queue.append(neighbor)
# Example graph (adjacency list)
graph = {
'A': ['B', 'C'],
'B': ['A', 'D', 'E'],
'C': ['A', 'F'],
'D': ['B'],
'E': ['B', 'F'],
'F': ['C', 'E']
}
bfs(graph, 'A')
Output: A B C D E F
The deque from collections is faster than a regular list for pop(0). Use it.
Implementation in JavaScript
function bfs(graph, start) {
const visited = new Set();
const queue = [start];
visited.add(start);
while (queue.length > 0) {
const node = queue.shift();
console.log(node);
for (const neighbor of graph[node]) {
if (!visited.has(neighbor)) {
visited.add(neighbor);
queue.push(neighbor);
}
}
}
}
// Example usage
const graph = {
'A': ['B', 'C'],
'B': ['A', 'D', 'E'],
'C': ['A', 'F'],
'D': ['B'],
'E': ['B', 'F'],
'F': ['C', 'E']
};
bfs(graph, 'A');
Finding Shortest Path with BFS
This is where BFS shines. Here's how to reconstruct the actual path:
from collections import deque
def bfs_shortest_path(graph, start, end):
visited = {start}
queue = deque([(start, [start])])
while queue:
node, path = queue.popleft()
if node == end:
return path
for neighbor in graph[node]:
if neighbor not in visited:
visited.add(neighbor)
queue.append((neighbor, path + [neighbor]))
return None # No path exists
# Usage
graph = {
'A': ['B', 'C'],
'B': ['A', 'D', 'E'],
'C': ['A', 'F'],
'D': ['B'],
'E': ['B', 'F'],
'F': ['C', 'E']
}
print(bfs_shortest_path(graph, 'A', 'F')) # ['A', 'C', 'F']
The path tracking adds minimal overhead but gives you the actual route, not just visited nodes.
Time and Space Complexity
These are the numbers you need to know:
- Time complexity: O(V + E) where V is vertices and E is edges
- Space complexity: O(V) for the visited set and queue
BFS visits every vertex and edge exactly once. That's optimal for traversal. The space cost is unavoidable if you want correct results.
Common Mistakes That Will Break Your BFS
- Forgetting the visited set: Infinite loops in cyclic graphs
- Marking visited too late: Mark when adding to queue, not when popping
- Using list instead of deque: list.pop(0) is O(n), deque.popleft() is O(1)
- Not handling disconnected graphs: Loop through all vertices as potential starting points
BFS on Binary Trees
Trees are just special graphs with no cycles. Level-order traversal is BFS:
from collections import deque
class TreeNode:
def __init__(self, val):
self.val = val
self.left = None
self.right = None
def level_order(root):
if not root:
return []
result = []
queue = deque([root])
while queue:
level_size = len(queue)
level = []
for _ in range(level_size):
node = queue.popleft()
level.append(node.val)
if node.left:
queue.append(node.left)
if node.right:
queue.append(node.right)
result.append(level)
return result
This groups nodes by level. Each iteration processes one entire level before moving down.
When BFS Is the Wrong Tool
BFS isn't always the answer:
- Deep graphs with few branches: DFS uses less memory
- Weighted shortest paths: Use Dijkstra's algorithm instead
- Finding any path quickly: DFS often finishes faster in practice
- Very large graphs: BFS can run out of memory before time
Know your constraints. A 10,000-node deep chain will kill BFS's queue but barely fazes DFS.
Grid-Based BFS Problems
Many BFS problems involve grids. Standard pattern:
def bfs_grid(grid, start):
rows, cols = len(grid), len(grid[0])
queue = deque([start])
visited = {start}
directions = [(0, 1), (0, -1), (1, 0), (-1, 0)]
while queue:
r, c = queue.popleft()
for dr, dc in directions:
nr, nc = r + dr, c + dc
if 0 <= nr < rows and 0 <= nc < cols and (nr, nc) not in visited:
visited.add((nr, nc))
queue.append((nr, nc))
return visited
This handles the boundary checking and neighbor iteration. Adapt the visited condition based on what counts as "traversable."
Bottom Line
BFS is straightforward: use a queue, mark visited nodes early, and process level by level. It guarantees shortest path in unweighted graphs. That's the entire game.
The implementation is short. The tricky part is recognizing when BFS applies. If you're finding shortest distance, exploring level-by-level, or checking connectivity—BFS is probably your answer.