DSA Tutorial
🔍

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

OperationTimeSpaceNotes
BFS traversalO(V + E)O(V)visited array + queue
Shortest pathO(V + E)O(V)dist array + queue
Path reconstructionO(V + E)O(V)parent array + queue
Connected componentsO(V + E)O(V)Same BFS, outer loop
Multi-source BFSO(V + E)O(V)All sources at distance 0
0-1 BFSO(V + E)O(V)Deque instead of queue
Cycle detectionO(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 pathdist[v] = dist[u] + 1 when v is first discovered; guaranteed to be minimum edge count
  • Path reconstruction — track parent[v] = u during BFS, then backtrack from destination to source

Four key BFS patterns:

PatternChange from standard BFS
Connected componentsOuter loop over all vertices; count BFS restarts
Multi-source BFSPre-load all sources at distance 0 before the loop
0-1 BFSReplace queue with deque; weight-0 → front, weight-1 → back
Cycle detectionTrack 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.

Suggested Quiz

BFS uses a queue. Why does FIFO order guarantee shortest paths in an unweighted graph?

1/6