Breadth First Search (BFS)
What Is BFS on a Graph?
BFS explores a graph level by level — first all neighbours of the start vertex, then their unvisited neighbours, and so on. It uses a queue (FIFO) to process vertices in the order they are discovered.
Reference graph (undirected):
0 ─── 1 ─── 3
│ │
2 ─── 4 ─── 5
BFS from vertex 0:
Level 0: [0] Enqueue 0, mark visited
Level 1: [1, 2] Process 0 → enqueue unvisited neighbours 1, 2
Level 2: [3, 4] Process 1 → enqueue 3 (2 already visited? no)
Process 2 → enqueue 4
Level 3: [5] Process 3 → no new
Process 4 → enqueue 5
BFS order: 0, 1, 2, 3, 4, 5
Distances from 0:
d[0]=0, d[1]=1, d[2]=1, d[3]=2, d[4]=2, d[5]=3
The first time BFS reaches a vertex = shortest path (in edge count).
Why BFS Gives Shortest Paths
KEY PROPERTY: BFS processes all distance-d vertices BEFORE
any distance-(d+1) vertex is dequeued.
Proof sketch:
- Start: only vertex s at distance 0 is in the queue.
- Processing s: enqueue all its neighbours at distance 1.
- FIFO: all distance-1 vertices are dequeued before any
distance-2 vertex (which is enqueued by distance-1 vertices).
- By induction: distance-k vertices are fully processed before
any distance-(k+1) vertex is dequeued.
- Therefore: first time vertex v is dequeued = shortest path to v.
If a vertex v is reachable via a shorter path,
that shorter path would have been discovered earlier (fewer levels)
and v would already be marked visited.
Any later rediscovery via a longer path is rejected.
This guarantee ONLY holds for unweighted graphs (all edges = cost 1).
For weighted graphs: use Dijkstra's algorithm.
Core BFS Implementation
1import java.util.*;
2
3public class GraphBFS {
4
5 // BFS traversal — returns vertices in BFS order
6 public static List<Integer> bfs(List<List<Integer>> adj, int start, int V) {
7 List<Integer> order = new ArrayList<>();
8 boolean[] visited = new boolean[V];
9 Queue<Integer> queue = new ArrayDeque<>();
10
11 visited[start] = true;
12 queue.offer(start);
13
14 while (!queue.isEmpty()) {
15 int u = queue.poll(); // Dequeue front vertex
16 order.add(u);
17
18 for (int v : adj.get(u)) {
19 if (!visited[v]) {
20 visited[v] = true; // Mark BEFORE enqueuing
21 queue.offer(v);
22 }
23 }
24 }
25
26 return order;
27 }
28
29 // BFS shortest path — returns distances from start
30 public static int[] shortestPath(List<List<Integer>> adj, int start, int V) {
31 int[] dist = new int[V];
32 boolean[] visited = new boolean[V];
33 Queue<Integer> queue = new ArrayDeque<>();
34
35 Arrays.fill(dist, -1); // -1 = unreachable
36 dist[start] = 0;
37 visited[start] = true;
38 queue.offer(start);
39
40 while (!queue.isEmpty()) {
41 int u = queue.poll();
42
43 for (int v : adj.get(u)) {
44 if (!visited[v]) {
45 visited[v] = true;
46 dist[v] = dist[u] + 1; // One more edge from u
47 queue.offer(v);
48 }
49 }
50 }
51
52 return dist;
53 }
54
55 // BFS shortest path with parent tracking — reconstruct actual path
56 public static List<Integer> shortestPathRoute(
57 List<List<Integer>> adj, int start, int end, int V) {
58 int[] parent = new int[V];
59 boolean[] visited = new boolean[V];
60 Queue<Integer> queue = new ArrayDeque<>();
61
62 Arrays.fill(parent, -1);
63 visited[start] = true;
64 queue.offer(start);
65
66 while (!queue.isEmpty()) {
67 int u = queue.poll();
68 if (u == end) break;
69
70 for (int v : adj.get(u)) {
71 if (!visited[v]) {
72 visited[v] = true;
73 parent[v] = u;
74 queue.offer(v);
75 }
76 }
77 }
78
79 // Reconstruct path from end to start via parent array
80 if (!visited[end]) return Collections.emptyList(); // No path
81
82 List<Integer> path = new ArrayList<>();
83 for (int v = end; v != -1; v = parent[v]) {
84 path.add(0, v); // Prepend to get start→end order
85 }
86 return path;
87 }
88
89 public static void main(String[] args) {
90 // Graph: 0─1─3, 0─2─4─5, 1─4
91 int V = 6;
92 List<List<Integer>> adj = new ArrayList<>();
93 for (int i = 0; i < V; i++) adj.add(new ArrayList<>());
94 int[][] edges = {{0,1},{0,2},{1,3},{1,4},{2,4},{4,5}};
95 for (int[] e : edges) {
96 adj.get(e[0]).add(e[1]); adj.get(e[1]).add(e[0]);
97 }
98
99 System.out.println("BFS order from 0: " + bfs(adj, 0, V));
100 // [0, 1, 2, 3, 4, 5]
101
102 int[] dist = shortestPath(adj, 0, V);
103 System.out.println("Distances from 0: " + Arrays.toString(dist));
104 // [0, 1, 1, 2, 2, 3]
105
106 List<Integer> path = shortestPathRoute(adj, 0, 5, V);
107 System.out.println("Path 0→5: " + path);
108 // [0, 2, 4, 5] or [0, 1, 4, 5]
109 }
110}Output:
BFS order from 0: [0, 1, 2, 3, 4, 5]
Distances from 0: [0, 1, 1, 2, 2, 3]
Path 0→5: [0, 2, 4, 5]
Dry Run: BFS Shortest Path
Graph:
0 ─── 1 ─── 3
│ │
2 ─── 4 ─── 5
BFS from 0:
Initial: queue=[0], visited={0}, dist=[0,-1,-1,-1,-1,-1]
Step 1: Dequeue 0
Neighbours: 1 (unvisited → enqueue), 2 (unvisited → enqueue)
queue=[1, 2], dist=[0,1,1,-1,-1,-1]
Step 2: Dequeue 1
Neighbours: 0 (visited), 3 (unvisited → enqueue), 4 (unvisited → enqueue)
queue=[2, 3, 4], dist=[0,1,1,2,2,-1]
Step 3: Dequeue 2
Neighbours: 0 (visited), 4 (already in queue/visited → skip)
queue=[3, 4], dist unchanged
Step 4: Dequeue 3
Neighbours: 1 (visited) → nothing new
queue=[4]
Step 5: Dequeue 4
Neighbours: 1 (visited), 2 (visited), 5 (unvisited → enqueue)
queue=[5], dist=[0,1,1,2,2,3]
Step 6: Dequeue 5
Neighbours: 4 (visited) → nothing new
queue=[]
Final distances: [0, 1, 1, 2, 2, 3]
All vertices visited — graph is connected ✓
Application 1: Connected Components
Run BFS from each unvisited vertex. Each BFS call discovers one complete connected component.
Disconnected graph:
Component 1: 0─1─2
Component 2: 3─4
Component 3: 5 (isolated)
Outer loop:
vertex 0: not visited → BFS → discovers {0,1,2} → component 1
vertex 1: already visited → skip
vertex 2: already visited → skip
vertex 3: not visited → BFS → discovers {3,4} → component 2
vertex 4: already visited → skip
vertex 5: not visited → BFS → discovers {5} → component 3
Total components: 3
1import java.util.*;
2
3public class ConnectedComponents {
4
5 public static int countComponents(List<List<Integer>> adj, int V) {
6 boolean[] visited = new boolean[V];
7 int count = 0;
8
9 for (int start = 0; start < V; start++) {
10 if (!visited[start]) {
11 bfsComponent(adj, start, visited); // Explore entire component
12 count++;
13 }
14 }
15 return count;
16 }
17
18 // BFS that labels all vertices in one component
19 public static List<Integer> getComponents(List<List<Integer>> adj, int V) {
20 int[] component = new int[V];
21 boolean[] visited = new boolean[V];
22 Arrays.fill(component, -1);
23 int compId = 0;
24
25 for (int start = 0; start < V; start++) {
26 if (!visited[start]) {
27 Queue<Integer> queue = new ArrayDeque<>();
28 queue.offer(start);
29 visited[start] = true;
30 component[start] = compId;
31
32 while (!queue.isEmpty()) {
33 int u = queue.poll();
34 for (int v : adj.get(u)) {
35 if (!visited[v]) {
36 visited[v] = true;
37 component[v] = compId;
38 queue.offer(v);
39 }
40 }
41 }
42 compId++;
43 }
44 }
45 return Arrays.asList(Arrays.stream(component).boxed().toArray(Integer[]::new));
46 }
47
48 private static void bfsComponent(List<List<Integer>> adj, int start, boolean[] visited) {
49 Queue<Integer> queue = new ArrayDeque<>();
50 queue.offer(start);
51 visited[start] = true;
52 while (!queue.isEmpty()) {
53 int u = queue.poll();
54 for (int v : adj.get(u)) {
55 if (!visited[v]) { visited[v] = true; queue.offer(v); }
56 }
57 }
58 }
59
60 public static void main(String[] args) {
61 // Disconnected: 0-1-2, 3-4, 5
62 int V = 6;
63 List<List<Integer>> adj = new ArrayList<>();
64 for (int i = 0; i < V; i++) adj.add(new ArrayList<>());
65 adj.get(0).add(1); adj.get(1).add(0);
66 adj.get(1).add(2); adj.get(2).add(1);
67 adj.get(3).add(4); adj.get(4).add(3);
68 // 5 is isolated
69
70 System.out.println("Components: " + countComponents(adj, V)); // 3
71 System.out.println("Labels: " + getComponents(adj, V)); // [0,0,0,1,1,2]
72 }
73}Output:
Components: 3
Labels: [0, 0, 0, 1, 1, 2]
Application 2: Multi-Source BFS
Start BFS with multiple sources simultaneously. Finds minimum distance from the nearest source to every vertex.
PROBLEM: "01 Matrix" — distance from each cell to nearest 0. Matrix: Distances to nearest 0: 0 0 0 0 0 0 0 1 0 → 0 1 0 1 1 1 1 2 1 ALGORITHM: Enqueue ALL zeros at distance 0. BFS from all zeros simultaneously. Each cell gets the distance from its nearest zero. vs SINGLE-SOURCE BFS from each zero separately: Would be O(K × V) where K = number of zeros. Multi-source BFS: O(V + E) — same as single BFS.
1import java.util.*;
2
3public class MultiSourceBFS {
4
5 // 01 Matrix: distance to nearest 0
6 public static int[][] updateMatrix(int[][] mat) {
7 int m = mat.length, n = mat[0].length;
8 int[][] dist = new int[m][n];
9 boolean[][] visited = new boolean[m][n];
10 Queue<int[]> queue = new ArrayDeque<>();
11
12 // Enqueue ALL zeros at distance 0
13 for (int i = 0; i < m; i++) {
14 for (int j = 0; j < n; j++) {
15 if (mat[i][j] == 0) {
16 dist[i][j] = 0;
17 visited[i][j] = true;
18 queue.offer(new int[]{i, j});
19 } else {
20 dist[i][j] = Integer.MAX_VALUE;
21 }
22 }
23 }
24
25 int[][] dirs = {{0,1},{0,-1},{1,0},{-1,0}};
26
27 while (!queue.isEmpty()) {
28 int[] curr = queue.poll();
29 int r = curr[0], c = curr[1];
30
31 for (int[] d : dirs) {
32 int nr = r + d[0], nc = c + d[1];
33 if (nr >= 0 && nr < m && nc >= 0 && nc < n && !visited[nr][nc]) {
34 visited[nr][nc] = true;
35 dist[nr][nc] = dist[r][c] + 1;
36 queue.offer(new int[]{nr, nc});
37 }
38 }
39 }
40
41 return dist;
42 }
43
44 public static void main(String[] args) {
45 int[][] mat = {{0,0,0},{0,1,0},{1,1,1}};
46 int[][] result = updateMatrix(mat);
47 for (int[] row : result) System.out.println(Arrays.toString(row));
48 // [0, 0, 0]
49 // [0, 1, 0]
50 // [1, 2, 1]
51 }
52}Output:
[0, 0, 0]
[0, 1, 0]
[1, 2, 1]
Application 3: 0-1 BFS
For graphs where edge weights are only 0 or 1, use a deque instead of a queue. Weight-0 edges go to the front; weight-1 edges go to the back. Achieves O(V + E) instead of O((V+E) log V) for Dijkstra's.
KEY IDEA:
Weight-0 edge: neighbour at SAME distance → front of deque (process next)
Weight-1 edge: neighbour at distance+1 → back of deque (process later)
GRAPH EXAMPLE:
0 ──0── 1 ──1── 2
│ │
└──1── 3 ──0── ┘
From 0:
0 is at dist 0
1 (via weight-0 edge): dist = 0 → FRONT
3 (via weight-1 edge): dist = 1 → BACK
Deque after processing 0: FRONT [1, 3] BACK
Process 1 (dist=0):
2 (weight-1): dist=1 → BACK
Deque: FRONT [3, 2] BACK
Process 3 (dist=1):
2 (weight-0): dist=1 → FRONT (better than current dist[2]=1? same, skip)
Final distances: [0, 0, 1, 1]
1import java.util.*;
2
3public class ZeroOneBFS {
4
5 // 0-1 BFS: edge weights are 0 or 1 only
6 public static int[] zeroOneBFS(List<List<int[]>> adj, int start, int V) {
7 int[] dist = new int[V];
8 Arrays.fill(dist, Integer.MAX_VALUE);
9 dist[start] = 0;
10
11 Deque<Integer> deque = new ArrayDeque<>();
12 deque.addFirst(start);
13
14 while (!deque.isEmpty()) {
15 int u = deque.pollFirst();
16
17 for (int[] edge : adj.get(u)) {
18 int v = edge[0], w = edge[1];
19
20 if (dist[u] + w < dist[v]) {
21 dist[v] = dist[u] + w;
22
23 if (w == 0) deque.addFirst(v); // Same level → FRONT
24 else deque.addLast(v); // Next level → BACK
25 }
26 }
27 }
28
29 return dist;
30 }
31
32 public static void main(String[] args) {
33 // Graph with 0/1 weights: 0─0─1─1─2, 0─1─3─0─2
34 int V = 4;
35 List<List<int[]>> adj = new ArrayList<>();
36 for (int i = 0; i < V; i++) adj.add(new ArrayList<>());
37 adj.get(0).add(new int[]{1, 0}); adj.get(1).add(new int[]{0, 0});
38 adj.get(1).add(new int[]{2, 1}); adj.get(2).add(new int[]{1, 1});
39 adj.get(0).add(new int[]{3, 1}); adj.get(3).add(new int[]{0, 1});
40 adj.get(3).add(new int[]{2, 0}); adj.get(2).add(new int[]{3, 0});
41
42 System.out.println(Arrays.toString(zeroOneBFS(adj, 0, V)));
43 // [0, 0, 1, 1]
44 }
45}Output:
[0, 0, 1, 1]
BFS Cycle Detection (Undirected)
In undirected BFS: if a neighbour of u is already visited
AND it is not u's direct parent → CYCLE exists.
0 ─── 1
│ │
2 ─── 3 ← square has a cycle
BFS from 0:
Process 0: enqueue 1, 2 (parent[1]=0, parent[2]=0)
Process 1: enqueue 3 (parent[3]=1); 0 is visited — 0 is parent of 1 → OK
Process 2: neighbour 3 is already visited (via 1) and 3 ≠ parent[2]=0 → CYCLE!
1public static boolean hasCycleBFS(List<List<Integer>> adj, int V) {
2 boolean[] visited = new boolean[V];
3
4 for (int start = 0; start < V; start++) {
5 if (visited[start]) continue;
6
7 Queue<int[]> queue = new ArrayDeque<>(); // [vertex, parent]
8 queue.offer(new int[]{start, -1});
9 visited[start] = true;
10
11 while (!queue.isEmpty()) {
12 int[] curr = queue.poll();
13 int u = curr[0];
14 int parent = curr[1];
15
16 for (int v : adj.get(u)) {
17 if (!visited[v]) {
18 visited[v] = true;
19 queue.offer(new int[]{v, u});
20 } else if (v != parent) {
21 return true; // Back edge → cycle
22 }
23 }
24 }
25 }
26 return false;
27}BFS Summary: Key Patterns
PATTERN TECHNIQUE COMPLEXITY ──────────────────────────────────────────────────────────────────── Single-source BFS Queue + visited array O(V + E) Shortest path distance dist[v] = dist[u] + 1 O(V + E) Reconstruct path parent[] array + backtrack O(V + E) Connected components Outer loop + BFS per component O(V + E) Multi-source BFS Pre-enqueue all sources O(V + E) 0-1 BFS Deque: 0-weight→front, 1→back O(V + E) Cycle detection Track parent in BFS pair O(V + E) Bipartite check Alternate colors in BFS O(V + E) Level-by-level levelSize = queue.size() O(V + E)
Complexity Summary
| Operation | Time | Space | Notes |
|---|---|---|---|
| BFS traversal | O(V + E) | O(V) | visited array + queue |
| Shortest path | O(V + E) | O(V) | dist array + queue |
| Path reconstruction | O(V + E) | O(V) | parent array + queue |
| Connected components | O(V + E) | O(V) | Same BFS, outer loop |
| Multi-source BFS | O(V + E) | O(V) | All sources at distance 0 |
| 0-1 BFS | O(V + E) | O(V) | Deque instead of queue |
| Cycle detection | O(V + E) | O(V) | Track parent per vertex |
V = vertices, E = edges. Assumes adjacency list representation.
Common Mistakes
Marking visited AFTER dequeuing instead of BEFORE enqueuing. If you mark a vertex visited when you dequeue it (instead of when you enqueue it), the same vertex can be enqueued multiple times — once from each neighbour that discovers it before it's dequeued. This causes O(E) redundant work in the worst case and incorrect distance calculations. Always mark visited immediately when enqueuing.
Forgetting the outer loop for disconnected graphs. BFS from vertex 0 only explores vertex 0's connected component. If the graph is disconnected, unvisited vertices are never reached. The outer loop for each unvisited vertex: start BFS is required to handle all components.
Using DFS instead of BFS for shortest path. DFS finds A path but not necessarily the shortest. BFS guarantees shortest path in unweighted graphs because it processes vertices level by level. Using DFS and hoping to find the minimum is incorrect — DFS may find a long path before the short one.
Using BFS for weighted shortest paths. BFS gives shortest path by edge count (unweighted). For weighted graphs, use Dijkstra's (positive weights) or Bellman-Ford (any weights). BFS on a weighted graph finds the path with fewest edges, not minimum total weight.
Multi-source BFS: running separate BFS for each source. Running K separate BFS calls each taking O(V + E) gives O(K × (V + E)) total — much worse than multi-source BFS O(V + E). Always use the single multi-source BFS by pre-loading all sources into the queue at distance 0.
Interview Questions
Q: Why does BFS guarantee shortest path but DFS does not?
BFS processes vertices level by level — all vertices at distance d are fully processed before any vertex at distance d+1 is dequeued. This is guaranteed by the FIFO property of the queue. The first time BFS reaches a vertex, it's via the shortest path (fewest edges). DFS follows one path as deep as possible, potentially finding a long path to a vertex before discovering a shorter one. DFS gives A path but not the SHORTEST path.
Q: What is the time complexity of BFS and why?
O(V + E) with adjacency list. Each vertex is enqueued and dequeued exactly once — O(V) total dequeue operations. Each edge is examined exactly once (from the source vertex's adjacency list when that vertex is dequeued) — O(E) total edge examinations. With adjacency matrix, BFS is O(V²) — each dequeue requires scanning V entries to find neighbours.
Q: How does multi-source BFS differ from running BFS from each source separately?
Multi-source BFS adds ALL sources to the queue at distance 0 before the main loop starts. This is equivalent to adding a virtual super-source connected to all real sources with weight-0 edges. The entire BFS runs once — O(V + E). Running separate BFS from each of K sources is O(K × (V + E)) — much slower. Multi-source BFS gives the minimum distance to the nearest source from every vertex in one pass.
Summary
BFS explores a graph level by level using a queue (FIFO). Vertices are marked visited when enqueued (not when dequeued) to prevent duplicate processing.
Three foundational results from BFS:
- ›Traversal order — all vertices reachable from source, in order of increasing distance
- ›Shortest path —
dist[v] = dist[u] + 1when v is first discovered; guaranteed to be minimum edge count - ›Path reconstruction — track
parent[v] = uduring BFS, then backtrack from destination to source
Four key BFS patterns:
| Pattern | Change from standard BFS |
|---|---|
| Connected components | Outer loop over all vertices; count BFS restarts |
| Multi-source BFS | Pre-load all sources at distance 0 before the loop |
| 0-1 BFS | Replace queue with deque; weight-0 → front, weight-1 → back |
| Cycle detection | Track parent; visited neighbour ≠ parent → cycle |
When BFS is the right tool:
- ›Shortest path in unweighted graphs — guaranteed minimum edges
- ›Level-by-level processing — all distance-k nodes before distance-(k+1)
- ›Multi-source minimum distance — nearest exit, rotten oranges, 01 matrix
- ›Connected component counting
In the next topic you will explore Graph DFS — depth-first traversal, cycle detection, topological sort, and strongly connected components.
BFS uses a queue. Why does FIFO order guarantee shortest paths in an unweighted graph?