BFS Breadth-First Search
Explore a graph level by level using a queue โ the first traversal to reach any node uses the fewest edges. BFS is the go-to algorithm for shortest path in unweighted graphs, multi-source expansion, and any problem asking for "minimum steps."
What is BFS?
Breadth-First Search starts at a source node and explores every neighbor at distance 1 before visiting any neighbor at distance 2, then distance 3, and so on โ always processing the closest unvisited nodes first. The core mechanism is a FIFO queue: dequeue a node, process it, enqueue all its unvisited neighbors. Because the queue enforces first-in-first-out order, every node is reached by the path with the fewest edges โ making BFS the natural algorithm for shortest-path problems in unweighted graphs. BFS runs in O(V + E) time and O(V) space for the queue and visited set โ it visits every vertex at most once and examines every edge at most once (twice for undirected). The algorithm terminates naturally when the queue is empty, meaning all reachable nodes have been explored. BFS extends from single graphs to 2D grids (where each cell is a vertex with edges to 4 or 8 neighbors), to multi-source problems (where multiple starting nodes are enqueued simultaneously), and to implicit state-space graphs (where configurations are vertices and transitions are edges).
How It Thinks
BFS โ Step by Step
- If you add a node to the visited set only when you dequeue it, multiple neighbors can enqueue the same node simultaneously โ causing duplicate processing and incorrect distance calculation.
- Always
visited.add(neighbor)at the moment you add it to the queue.
- At the start of processing a level,
int size = queue.size()captures exactly how many nodes belong to this level. - The inner for-loop processes exactly that many nodes โ any new nodes enqueued during this loop belong to the next level.
- Without this snapshot, you cannot distinguish levels in the queue.
Shortest Path โ Why BFS Guarantees It
BFS finds the shortest path (minimum number of edges) in an unweighted graph because of FIFO ordering: a node at distance d is always dequeued and processed before any node at distance d+1. The first time BFS reaches a node, no shorter path exists โ if there were a shorter path, that node would have been enqueued (and dequeued) earlier. This guarantee does NOT hold for weighted graphs โ if edges have different costs, Dijkstra's algorithm is needed. For unweighted graphs, BFS is both optimal and O(V+E) โ no algorithm can do better.
- Once BFS dequeues the target node, stop immediately โ you have the shortest path. Processing further nodes wastes time.
- For problems asking only "is target reachable?" or "what is the distance?", check target equality right after dequeue.
Multi-Source BFS โ Expand From Everywhere
Standard BFS starts from a single source. Multi-source BFS starts from ALL given sources simultaneously โ enqueue every source node at level 0, then expand outward level by level. The result: each non-source node is assigned the distance to its nearest source in O(V+E) total. Without multi-source BFS, you would run single-source BFS from each source separately โ O(S ร (V+E)) where S is the number of sources. Multi-source BFS is the key to solving "Rotting Oranges" (LC 994), "01 Matrix" (LC 542), "Walls and Gates" (LC 286), and any problem asking "distance to nearest X."
- When a problem asks "what is the minimum distance from any cell of type X to reach cell Y" or "how long until all cells are affected" โ think multi-source BFS.
- Enqueue all type-X cells at level 0, expand outward. One pass. O(V+E).
BFS on 2D Grids
A 2D grid is an implicit graph where each cell is a vertex and edges connect to 4-directional (or 8-directional) neighbors. You never build an adjacency list โ instead, use a directions array int[][] dirs = {{0,1},{0,-1},{1,0},{-1,0}} and iterate. Grid BFS is the dominant pattern in LeetCode graph problems โ "Number of Islands," "Shortest Path in Binary Matrix," "01 Matrix," and "Walls and Gates" all use it. The main trap is bounds checking: always verify 0 <= nr < rows && 0 <= nc < cols before accessing the grid.
- Instead of a separate
boolean[][], you can modify the grid in-place โ change visited cells to a sentinel value (e.g., -1 or '#'). - This saves O(V) space but mutates the input. In interviews, clarify with the interviewer whether mutation is allowed.
BFS vs DFS โ When to Use Which
BFS and DFS both traverse all reachable vertices in O(V+E), but they answer different questions. BFS finds the shortest path (minimum edges) and processes nodes level by level โ use it when the problem asks for "minimum steps," "nearest," or "shortest." DFS explores depth-first and backtracks โ use it when the problem asks for "all paths," "connected components," "cycle detection," or "topological sort." The wrong choice doesn't just give a suboptimal solution โ it gives the wrong answer entirely. Using DFS for "minimum steps" yields a valid path but not the shortest one.
| Property | BFS | DFS |
|---|---|---|
| Data structure | Queue (FIFO) | Stack / recursion (LIFO) |
| Order | Level by level | Depth first, then backtrack |
| Shortest path (unweighted) | โ Guaranteed | โ Not guaranteed |
| Space | O(width) โ wide levels = high memory | O(depth) โ deep paths = high stack |
| Cycle detection | Possible but DFS is more natural | โ Natural with 3-color scheme |
| Topological sort | โ Kahn's algorithm (in-degree) | โ Reverse postorder |
| Connected components | โ Works | โ Works (often preferred) |
| All paths | โ Expensive | โ Natural with backtracking |
Shortest / Minimum / Nearest
- Shortest path in unweighted graph
- Minimum number of steps/moves
- Nearest distance to target
- Level-order tree traversal
- Multi-source expansion (rotten oranges)
Explore / Detect / Order
- Find all paths or enumerate solutions
- Detect cycles in directed graphs
- Topological sort (reverse postorder)
- Flood fill / connected components
- Backtracking problems
Build It Once
Build this from scratch once โ it makes the mechanics concrete. In practice, BFS is boilerplate: queue + visited + neighbor loop. Memorise the template.
Use It In Java
Queue<Integer> q = new LinkedList<>() or new ArrayDeque<>() Set<Integer> visited = new HashSet<>() or boolean[] visited import java.util.*; | Method | Complexity | Note |
|---|---|---|
queue.add(x) / queue.offer(x) | O(1) | Enqueue โ add throws on full, offer returns false |
queue.poll() | O(1) | Dequeue โ returns null if empty |
queue.peek() | O(1) | Look at front without removing |
queue.size() | O(1) | Level snapshot for level-by-level BFS |
queue.isEmpty() | O(1) | BFS termination check |
visited.add(x) | O(1) avg | Returns false if already present โ doubles as check+add |
map.computeIfAbsent(k, k2->new ArrayList<>()) | O(1) | Build adjacency list from edge list |
ArrayDeque is faster than LinkedList for BFS queues (no node allocation overhead), but ArrayDeque does not allow null elements. Since graph BFS never enqueues null, always prefer ArrayDeque โ it is ~30% faster in practice. Despite this, most LeetCode solutions still use LinkedList by habit.
ArrayDeque for BFS queue in all cases except when you need null sentinel values. Use boolean[] visited when vertices are 0-indexed integers (faster than HashSet). Use HashSet visited when vertices are objects or sparse IDs. Problems To Solve
BFS interview problems fall into three patterns: single-source shortest path ("minimum number of steps to reach target"), multi-source expansion ("how long until all cells are affected"), and implicit graph BFS ("state-space search where nodes are configurations"). Recognise the BFS signal: any problem asking for "minimum," "shortest," "nearest," or "fewest moves" in an unweighted context is almost certainly BFS.
| Difficulty | Pattern | Problem | Key Insight |
|---|---|---|---|
| Medium | bfs | Number of Islands โ LC 200 | Each '1' cell is a vertex. BFS (or DFS) from each unvisited '1' marks the entire island. Count how many times you initiate a new traversal. |
| Medium | bfs | Rotting Oranges โ LC 994 | Multi-source BFS: enqueue all rotten oranges at time 0. Each level of BFS is one minute of rot spreading. Track remaining fresh count. |
| Medium | bfs | 01 Matrix โ LC 542 | Multi-source BFS from all 0-cells. BFS levels give the distance to the nearest 0 for every 1-cell. Reverse the perspective: don't search from 1s, expand from 0s. |
| Medium | bfs | Shortest Path in Binary Matrix โ LC 1091 | Grid BFS with 8-directional neighbors from (0,0) to (n-1,n-1). Level count = shortest path length. Only move through 0-cells. |
| Hard | bfs | Word Ladder โ LC 127 | Implicit graph: each word is a vertex, edges connect words differing by one character. BFS from beginWord finds the shortest transformation chain. Generate mutations instead of comparing all pairs. |
| Hard | bfs | Sliding Puzzle โ LC 773 | State-space BFS: each board configuration is a vertex, each valid tile swap is an edge. Encode the board as a string for the visited set. BFS finds the minimum number of moves. |
- When you see "minimum steps," "shortest path," or "nearest distance" in an unweighted graph or grid โ use BFS.
- Start by identifying what the vertices are (cells? words? board states?) and what the edges are (adjacent cells? one-character mutations? tile swaps?).
- Build the queue, mark visited before enqueueing, and count levels. That pattern alone solves 80% of BFS problems.