Graph

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."

Foundations ยท Array ยท Linked List ยท Stack ยท Queue ยท HashMap ยท Binary Tree ยท BST ยท Heap ยท Trie ยท Graph ยท Sorting ยท Binary Search ยท Two Pointers ยท Sliding Window ยท Dynamic Prog.
01
Section One ยท Foundation

What is BFS?

Level 2 Level 1 L0 S A B C D E F G H I BFS expands outward like a ripple โ€” level by level

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).

Analogy: Drop a stone in a still pond. The ripple expands outward in concentric circles โ€” every point at distance 1 from the drop is reached before any point at distance 2, and every point at distance 2 before any point at distance 3. BFS works identically: the "ripple" is the queue, and each ring is one level of exploration. The nearest shore from the drop point is always hit by the first ring that reaches land โ€” that's why BFS finds the shortest path.
02
Section Two ยท Mental Model

How It Thinks

Queue enforces FIFO โ€” nodes enqueued first are dequeued first
โ–ถ
Closer nodes (fewer edges from source) are always processed before farther nodes โ€” guaranteeing shortest path in unweighted graphs
Each node is enqueued at most once (visited check before enqueue)
โ–ถ
O(V + E) total time โ€” every vertex is dequeued once, every edge is examined once from each endpoint
All neighbors of a node are enqueued at the same "level"
โ–ถ
BFS naturally groups nodes by distance โ€” enabling level-by-level processing (snap queue.size() at the start of each level)
The visited set is checked BEFORE enqueueing, not after dequeueing
โ–ถ
Prevents duplicate enqueues โ€” without this, the same node appears in the queue multiple times, wasting time and yielding wrong shortest-path distances
Multiple sources can be enqueued at level 0 simultaneously
โ–ถ
Multi-source BFS โ€” expansion proceeds from ALL sources at once, finding the nearest source to every other node in a single O(V+E) pass
BFS uses O(V) space for queue โ€” at worst, one entire level of the graph sits in memory
โ–ถ
In wide graphs (grids, social networks), memory usage can be large โ€” but BFS still outperforms DFS for shortest-path because DFS has no distance guarantee
03
Section Three ยท The Algorithm

BFS โ€” Step by Step

Standard BFS
Traverse all vertices reachable from a source, processing each exactly once in order of increasing distance from source.
Pseudocode
function bfs(graph, start): visited = {start} queue = [start] while queue is not empty: node = queue.dequeue() process(node) for each neighbor in graph[node]: if neighbor not in visited: visited.add(neighbor) // mark BEFORE enqueue queue.enqueue(neighbor) // O(V + E)
BFS execution โ€” queue state and visit order from node 0
0 1 2 3 4 dist=0 dist=1 dist=2 BFS Execution Trace Init: visited={0} queue=[0] Dequeue 0: neighbors = [1, 2] โ†’ enqueue 1, 2 ยท visited={0,1,2} ยท queue=[1,2] Dequeue 1: neighbors = [0, 3, 4] โ†’ 0 visited, enqueue 3, 4 ยท visited={0,1,2,3,4} ยท queue=[2,3,4] Dequeue 2: neighbors = [0, 4] โ†’ 0 visited, 4 visited โ€” enqueue nothing ยท queue=[3,4] Dequeue 3: all neighbors visited ยท queue=[4] Dequeue 4: all neighbors visited ยท queue=[] ยท DONE Visit order: 0 โ†’ 1 โ†’ 2 โ†’ 3 โ†’ 4 Distances from 0: {0:0, 1:1, 2:1, 3:2, 4:2} Total: 5 dequeues + each edge examined once from each side = O(V+E)
Mark visited BEFORE enqueueing, not after dequeueing:
  • 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.
Level-by-Level BFS
Process all nodes at the same distance from the source as one batch โ€” essential when the answer depends on "which level" (minimum steps, number of rounds).
Pseudocode
function bfsLevels(graph, start): visited = {start} queue = [start] level = 0 while queue is not empty: size = queue.length() // snapshot current level size for i = 0 to size - 1: node = queue.dequeue() process(node, level) for each neighbor in graph[node]: if neighbor not in visited: visited.add(neighbor) queue.enqueue(neighbor) level = level + 1 // O(V + E)
The size snapshot is the key:
  • 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.
Common Mistake: Forgetting to mark a node as visited when enqueueing on a graph with cycles. In a tree, there's only one path to each node so you won't revisit. In a graph, multiple paths lead to the same node โ€” without the visited check, BFS enters an infinite loop as it keeps re-enqueueing nodes it has already processed.
04
Section Four ยท Shortest Path

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.

Shortest Path with Path Reconstruction
Track the parent of each node during BFS to reconstruct the shortest path from source to target by following parents backward.
Pseudocode
function shortestPath(graph, start, target): visited = {start} parent = {start: null} queue = [start] while queue is not empty: node = queue.dequeue() if node == target: return reconstructPath(parent, target) for each neighbor in graph[node]: if neighbor not in visited: visited.add(neighbor) parent[neighbor] = node // record how we got here queue.enqueue(neighbor) return null // no path exists function reconstructPath(parent, target): path = [] node = target while node != null: path.addFirst(node) node = parent[node] return path // O(path length)
Shortest path from 0 โ†’ 4 โ€” BFS parent tracking
0 1 2 3 5 4 target Parent Map: parent[1] = 0 parent[3] = 1 parent[4] = 3 Path: 4โ†’3โ†’1โ†’0 Reversed: 0โ†’1โ†’3โ†’4 Distance: 3 edges BFS finds this path in O(V+E) โ€” no shorter path exists
Early termination on target:
  • 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.
05
Section Five ยท Multi-Source BFS

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."

Pseudocode
function multiSourceBFS(grid, sources): queue = [] visited = new Set() for each source in sources: // enqueue ALL sources at level 0 queue.enqueue(source) visited.add(source) level = 0 while queue is not empty: size = queue.length() for i = 0 to size - 1: cell = queue.dequeue() for each neighbor of cell: if neighbor not in visited: visited.add(neighbor) distance[neighbor] = level + 1 queue.enqueue(neighbor) level = level + 1 // O(V + E)
Multi-Source BFS โ€” "Rotting Oranges" pattern (sources = all rotten at t=0)
t = 0 ๐ŸŠ โ— โ— โ— โ— โ— โ— ๐ŸŠ t = 1 ๐ŸŠ ๐ŸŠ โ— โ— ๐ŸŠ โ— ๐ŸŠ ๐ŸŠ t = 2 โ€” all rotten ๐ŸŠ ๐ŸŠ ๐ŸŠ ๐ŸŠ ๐ŸŠ ๐ŸŠ ๐ŸŠ ๐ŸŠ Two rotten oranges enqueued at t=0 โ†’ BFS spreads rot from BOTH simultaneously Answer = number of BFS levels = 2 minutes Multi-source BFS = single BFS with multiple starting nodes in the initial queue Same O(V+E) complexity โ€” no repeated work
Multi-source BFS signal:
  • 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).
06
Section Six ยท BFS on Grids

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.

Java โ€” Grid BFS Template
int[][] dirs = {{0,1}, {0,-1}, {1,0}, {-1,0}}; int bfsGrid(int[][] grid, int sr, int sc, int tr, int tc) { int rows = grid.length, cols = grid[0].length; boolean[][] visited = new boolean[rows][cols]; Queue<int[]> queue = new LinkedList<>(); queue.add(new int[]{sr, sc}); visited[sr][sc] = true; int steps = 0; while (!queue.isEmpty()) { int size = queue.size(); // level snapshot for (int i = 0; i < size; i++) { int[] cell = queue.poll(); if (cell[0] == tr && cell[1] == tc) return steps; // found target for (int[] d : dirs) { int nr = cell[0] + d[0], nc = cell[1] + d[1]; if (nr >= 0 && nr < rows && nc >= 0 && nc < cols && !visited[nr][nc] && grid[nr][nc] != 1) { visited[nr][nc] = true; queue.add(new int[]{nr, nc}); } } } steps++; } return -1; // unreachable }
Grid BFS visited optimization:
  • 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.
07
Section Seven ยท BFS vs DFS

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
Use BFS when

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)
Use DFS when

Explore / Detect / Order

  • Find all paths or enumerate solutions
  • Detect cycles in directed graphs
  • Topological sort (reverse postorder)
  • Flood fill / connected components
  • Backtracking problems
Shortest path? minimum steps? Yes BFS No Cycle / ordering / all paths? Either works (connectivity, flood fill) Yes DFS
08
Section Eight ยท Implementation

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.

Java โ€” BFS core mechanics
import java.util.*; class BFS { // Standard BFS โ€” adjacency list โ€” O(V+E) static List<Integer> bfs(Map<Integer, List<Integer>> graph, int start) { List<Integer> order = new ArrayList<>(); Set<Integer> visited = new HashSet<>(); Queue<Integer> queue = new LinkedList<>(); visited.add(start); queue.add(start); while (!queue.isEmpty()) { int node = queue.poll(); order.add(node); for (int nei : graph.getOrDefault(node, List.of())) { if (visited.add(nei)) // returns false if already in set queue.add(nei); } } return order; } // Shortest distance โ€” O(V+E) static int shortestDist(Map<Integer, List<Integer>> graph, int src, int dst) { Map<Integer, Integer> dist = new HashMap<>(); Queue<Integer> queue = new LinkedList<>(); dist.put(src, 0); queue.add(src); while (!queue.isEmpty()) { int node = queue.poll(); if (node == dst) return dist.get(dst); for (int nei : graph.getOrDefault(node, List.of())) { if (!dist.containsKey(nei)) { dist.put(nei, dist.get(node) + 1); queue.add(nei); } } } return -1; } // Grid BFS โ€” O(rows ร— cols) static int gridBFS(int[][] grid, int sr, int sc, int tr, int tc) { int[][] dirs = {{0,1},{0,-1},{1,0},{-1,0}}; int R = grid.length, C = grid[0].length; boolean[][] vis = new boolean[R][C]; Queue<int[]> q = new LinkedList<>(); q.add(new int[]{sr, sc}); vis[sr][sc] = true; int steps = 0; while (!q.isEmpty()) { for (int sz = q.size(); sz > 0; sz--) { int[] c = q.poll(); if (c[0]==tr && c[1]==tc) return steps; for (int[] d : dirs) { int nr=c[0]+d[0], nc=c[1]+d[1]; if (nr>=0 && nr<R && nc>=0 && nc<C && !vis[nr][nc] && grid[nr][nc]==0) { vis[nr][nc] = true; q.add(new int[]{nr,nc}); } } } steps++; } return -1; } }
09
Section Nine ยท Java Reference

Use It In Java

IN JAVA โ€” BFS Building Blocks
Queue Queue<Integer> q = new LinkedList<>() or new ArrayDeque<>()
Visited Set<Integer> visited = new HashSet<>() or boolean[] visited
Import 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
โš  Gotcha: 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.
Choose Use 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.
10
Section Ten ยท Practice

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.
Interview Pattern:
  • 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.

โ†’ See the full BFS practice set