Skip to content
advancedPhase 5 · Graphs

BFS

Master breadth-first search for shortest path and level-order problems.

1h 15m
6 problems
Topic Progress0%

BFS Fundamentals and Implementation

How BFS Works

BFS explores vertices level by level, visiting all neighbors at the current depth before moving deeper. It uses a queue (FIFO) to maintain the frontier.

Graph:        0
             / \
            1   2
           / \\   \
          3   4   5

BFS Order: 0 → 1 → 2 → 3 → 4 → 5
Level 0: [0]
Level 1: [1, 2]
Level 2: [3, 4, 5]

Core Algorithm

import java.util.*;

public class BFS {
    
    // Standard BFS traversal
    public List<Integer> bfs(Map<Integer, List<Integer>> graph, int start) {
        List<Integer> result = new ArrayList<>();
        Set<Integer> visited = new HashSet<>();
        Queue<Integer> queue = new LinkedList<>();
        
        queue.offer(start);
        visited.add(start);
        
        while (!queue.isEmpty()) {
            int node = queue.poll();
            result.add(node);
            
            for (int neighbor : graph.get(node)) {
                if (!visited.contains(neighbor)) {
                    visited.add(neighbor);
                    queue.offer(neighbor);
                }
            }
        }
        
        return result;
    }
    
    // BFS with level tracking
    public List<List<Integer>> bfsByLevel(Map<Integer, List<Integer>> graph, int start) {
        List<List<Integer>> levels = new ArrayList<>();
        Set<Integer> visited = new HashSet<>();
        Queue<Integer> queue = new LinkedList<>();
        
        queue.offer(start);
        visited.add(start);
        
        while (!queue.isEmpty()) {
            int levelSize = queue.size();
            List<Integer> currentLevel = new ArrayList<>();
            
            for (int i = 0; i < levelSize; i++) {
                int node = queue.poll();
                currentLevel.add(node);
                
                for (int neighbor : graph.get(node)) {
                    if (!visited.contains(neighbor)) {
                        visited.add(neighbor);
                        queue.offer(neighbor);
                    }
                }
            }
            
            levels.add(currentLevel);
        }
        
        return levels;
    }
}

BFS Template for Interview Problems

// Complete BFS template with distance tracking
public int bfsShortestPath(Map<Integer, List<Integer>> graph, int start, int target) {
    if (start == target) return 0;
    
    Set<Integer> visited = new HashSet<>();
    Queue<Integer> queue = new LinkedList<>();
    Map<Integer, Integer> distance = new HashMap<>();
    
    queue.offer(start);
    visited.add(start);
    distance.put(start, 0);
    
    while (!queue.isEmpty()) {
        int node = queue.poll();
        
        for (int neighbor : graph.get(node)) {
            if (!visited.contains(neighbor)) {
                visited.add(neighbor);
                distance.put(neighbor, distance.get(node) + 1);
                queue.offer(neighbor);
                
                if (neighbor == target) {
                    return distance.get(neighbor);
                }
            }
        }
    }
    
    return -1; // Target not reachable
}

Complexity Analysis

Operation Time Space
BFS Traversal O(V + E) O(V)
Shortest Path (unweighted) O(V + E) O(V)
Level-by-Level O(V + E) O(V)

Key Properties

  1. Shortest Path: BFS guarantees shortest path in unweighted graphs
  2. Complete: BFS will find a solution if one exists
  3. Optimal: Among all algorithms with the same completeness, BFS finds the shallowest goal

Advanced BFS Patterns

Multi-Source BFS

When you need to find distances from multiple starting points simultaneously.

// Example: Rotting Oranges - multi-source BFS
public int orangesRotting(int[][] grid) {
    int rows = grid.length, cols = grid[0].length;
    Queue<int[]> queue = new LinkedList<>();
    int fresh = 0;
    
    // Add all rotten oranges as sources
    for (int r = 0; r < rows; r++) {
        for (int c = 0; c < cols; c++) {
            if (grid[r][c] == 2) {
                queue.offer(new int[]{r, c});
            } else if (grid[r][c] == 1) {
                fresh++;
            }
        }
    }
    
    if (fresh == 0) return 0;
    
    int[][] dirs = {{-1,0}, {1,0}, {0,-1}, {0,1}};
    int minutes = 0;
    
    while (!queue.isEmpty()) {
        int size = queue.size();
        boolean rotted = false;
        
        for (int i = 0; i < size; i++) {
            int[] cell = queue.poll();
            for (int[] dir : dirs) {
                int nr = cell[0] + dir[0];
                int nc = cell[1] + dir[1];
                
                if (nr >= 0 && nr < rows && nc >= 0 && nc < cols && grid[nr][nc] == 1) {
                    grid[nr][nc] = 2;
                    fresh--;
                    rotted = true;
                    queue.offer(new int[]{nr, nc});
                }
            }
        }
        
        if (rotted) minutes++;
    }
    
    return fresh == 0 ? minutes : -1;
}

0-1 BFS (Weighted Graphs with Two Weights)

When edges have only two possible weights (0 or 1), use a deque instead of a queue.

// 0-1 BFS using deque
public int zeroOneBFS(List<int[]>[] graph, int start, int n) {
    int[] dist = new int[n];
    Arrays.fill(dist, Integer.MAX_VALUE);
    dist[start] = 0;
    
    Deque<Integer> deque = new ArrayDeque<>();
    deque.offerFirst(start);
    
    while (!deque.isEmpty()) {
        int node = deque.pollFirst();
        
        for (int[] edge : graph[node]) {
            int neighbor = edge[0];
            int weight = edge[1];
            
            if (dist[node] + weight < dist[neighbor]) {
                dist[neighbor] = dist[node] + weight;
                if (weight == 0) {
                    deque.offerFirst(neighbor);
                } else {
                    deque.offerLast(neighbor);
                }
            }
        }
    }
    
    return dist;
}

BFS on Grid

// BFS on 2D grid - common pattern
public int bfsGrid(int[][] grid, int startR, int startC) {
    int rows = grid.length, cols = grid[0].length;
    boolean[][] visited = new boolean[rows][cols];
    Queue<int[]> queue = new LinkedList<>();
    
    queue.offer(new int[]{startR, startC});
    visited[startR][startC] = true;
    int steps = 0;
    
    int[][] dirs = {{-1,0}, {1,0}, {0,-1}, {0,1}};
    
    while (!queue.isEmpty()) {
        int size = queue.size();
        for (int i = 0; i < size; i++) {
            int[] cell = queue.poll();
            
            // Process cell here
            if (isTarget(cell[0], cell[1])) return steps;
            
            for (int[] dir : dirs) {
                int nr = cell[0] + dir[0];
                int nc = cell[1] + dir[1];
                
                if (nr >= 0 && nr < rows && nc >= 0 && nc < cols 
                    && !visited[nr][nc] && grid[nr][nc] != 0) {
                    visited[nr][nc] = true;
                    queue.offer(new int[]{nr, nc});
                }
            }
        }
        steps++;
    }
    
    return -1;
}

Common BFS Problems

Problem Pattern
Shortest Path in Unweighted Graph Standard BFS
Word Ladder BFS on implicit graph
Rotting Oranges Multi-source BFS
Open the Lock BFS on state space
Binary Tree Level Order BFS on tree
Knight's Tour BFS on grid

Interactive Visualization

Breadth-First Search (BFS) Traversal

Press Play or Step to begin
CurrentFound / DoneEliminatedUnvisited

Practice Problems

0/3solved
Word Ladder
BFS on Implicit Graph

Given two words (beginWord and endWord) and a word dictionary, find the length of the shortest transformation sequence from beginWord to endWord, changing only one letter at a time. Each intermediate word must be in the dictionary.

Example:

Input: beginWord = "hit", endWord = "cog", wordList = ["hot","dot","dog","lot","log","cog"]

Output: 5

hit → hot → dot → dog → cog (5 transformations)

Solution
```java
public int ladderLength(String beginWord, String endWord, List<String> wordList) {
    Set<String> wordSet = new HashSet<>(wordList);
    if (!wordSet.contains(endWord)) return 0;
    
    Queue<String> queue = new LinkedList<>();
    queue.offer(beginWord);
    Set<String> visited = new HashSet<>();
    visited.add(beginWord);
    int level = 1;
    
    while (!queue.isEmpty()) {
        int size = queue.size();
        for (int i = 0; i < size; i++) {
            String word = queue.poll();
            char[] chars = word.toCharArray();
            
            for (int j = 0; j < chars.length; j++) {
                char original = chars[j];
                for (char c = 'a'; c <= 'z'; c++) {
                    if (c == original) continue;
                    chars[j] = c;
                    String newWord = new String(chars);
                    
                    if (newWord.equals(endWord)) return level + 1;
                    
                    if (wordSet.contains(newWord) && !visited.contains(newWord)) {
                        visited.add(newWord);
                        queue.offer(newWord);
                    }
                }
                chars[j] = original;
            }
        }
        level++;
    }
    
    return 0;
}
```

Edge Cases:

  • beginWord equals endWord (return 0)
  • endWord not in wordList (return 0)
  • Single character transformation needed
  • Large word dictionary - need efficient lookup
Rotting Oranges
Multi-Source BFS

In a grid, each cell can be 0 (empty), 1 (fresh orange), or 2 (rotten orange). Every minute, rotten oranges rot adjacent fresh oranges. Return the minimum minutes until no fresh orange remains.

Example:

Input: grid = [[2,1,1],[1,1,0],[0,1,1]]

Output: 4

All oranges rot in 4 minutes.

Solution
```java
public int orangesRotting(int[][] grid) {
    Queue<int[]> queue = new LinkedList<>();
    int fresh = 0;
    for (int i = 0; i < grid.length; i++)
        for (int j = 0; j < grid[0].length; j++) {
            if (grid[i][j] == 2) queue.offer(new int[]{i, j});
            if (grid[i][j] == 1) fresh++;
        }
    if (fresh == 0) return 0;
    int[][] dirs = {{0,1},{0,-1},{1,0},{-1,0}};
    int minutes = 0;
    while (!queue.isEmpty()) {
        int size = queue.size();
        boolean rotted = false;
        for (int i = 0; i < size; i++) {
            int[] cell = queue.poll();
            for (int[] d : dirs) {
                int nr = cell[0]+d[0], nc = cell[1]+d[1];
                if (nr>=0 && nr<grid.length && nc>=0 && nc<grid[0].length && grid[nr][nc]==1) {
                    grid[nr][nc] = 2;
                    fresh--;
                    rotted = true;
                    queue.offer(new int[]{nr, nc});
                }
            }
        }
        if (rotted) minutes++;
    }
    return fresh == 0 ? minutes : -1;
}
```

Edge Cases:

  • No fresh oranges
  • Some oranges unreachable
  • All rotten
Open the Lock
BFS on States

You have a lock with 4 circular wheels. Each wheel has digits 0-9. Each turn moves one wheel one step. Return the minimum turns to reach target from 0000.

Example:

Input: deadends = ["0201","0101","0102","1212","2002"], target = "0202"

Output: 6

Minimum 6 turns to reach target.

Solution
```java
public int openLock(String[] deadends, String target) {
    Set<String> dead = new HashSet<>(Arrays.asList(deadends));
    Set<String> visited = new HashSet<>();
    if (dead.contains("0000")) return -1;
    Queue<String> queue = new LinkedList<>();
    queue.offer("0000");
    visited.add("0000");
    int steps = 0;
    while (!queue.isEmpty()) {
        int size = queue.size();
        for (int i = 0; i < size; i++) {
            String curr = queue.poll();
            if (curr.equals(target)) return steps;
            for (int j = 0; j < 4; j++) {
                for (int d = -1; d <= 1; d += 2) {
                    char[] arr = curr.toCharArray();
                    arr[j] = (char) ((arr[j] - '0' + d + 10) % 10 + '0');
                    String next = new String(arr);
                    if (!visited.contains(next) && !dead.contains(next)) {
                        visited.add(next);
                        queue.offer(next);
                    }
                }
            }
        }
        steps++;
    }
    return -1;
}
```

Edge Cases:

  • Target is deadend
  • Target is 0000
  • All neighbors dead

Quiz

1. Why does BFS guarantee the shortest path in unweighted graphs?

Question 1 options

2. In a multi-source BFS, when should you add all sources to the queue initially vs processing them one by one?

Question 2 options

3. What is the primary purpose of Breadth-First Search (BFS)?

Question 3 options

4. What is a common mistake when implementing Breadth-First Search (BFS)?

Question 4 options

Flashcards

Question

What data structure does BFS use and why?

Answer

BFS uses a queue (FIFO) because it processes nodes in the order they were discovered - first in, first out. This ensures level-by-level exploration.

Question

When should you use BFS vs DFS for finding a path?

Answer

Use BFS when you need the shortest path in an unweighted graph. BFS guarantees the shortest path. Use DFS when you just need any path, or when the graph is very deep and BFS would use too much memory.

Question

What is Breadth-First Search (BFS)?

Answer

Breadth-First Search (BFS) is a key concept in software engineering.

Question

When to use Breadth-First Search (BFS)?

Answer

Use Breadth-First Search (BFS) when building production systems that require reliability, scalability, and maintainability.

Question

Breadth-First Search (BFS) best practices

Answer

Follow SOLID principles, write clean code, test thoroughly, document decisions, and monitor in production.

Revision Notes

Key Takeaways

  • 1.BFS uses a queue and explores level by level
  • 2.BFS guarantees shortest path in unweighted graphs
  • 3.Track visited nodes to prevent revisiting
  • 4.Multi-source BFS: add all sources to queue simultaneously
  • 5.For grids, use direction arrays for cleaner neighbor generation

Interview Tips

  • BFS is your go-to for shortest path in unweighted graphs
  • Use level-by-level BFS when you need to process by distance
  • Multi-source BFS is common - practice Rotting Oranges and similar problems
  • For implicit graphs (Word Ladder), define state transitions carefully
  • Remember to mark visited BEFORE adding to queue to avoid duplicates

Cheat Sheet

BFS Cheat Sheet

Standard BFS Template

Queue<Integer> queue = new LinkedList<>();
Set<Integer> visited = new HashSet<>();
queue.offer(start);
visited.add(start);

while (!queue.isEmpty()) {
    int node = queue.poll();
    for (int neighbor : graph.get(node)) {
        if (!visited.contains(neighbor)) {
            visited.add(neighbor);
            queue.offer(neighbor);
        }
    }
}

BFS with Distance

Map<Integer, Integer> dist = new HashMap<>();
dist.put(start, 0);
// ... in loop:
dist.put(neighbor, dist.get(node) + 1);

Level-by-Level BFS

while (!queue.isEmpty()) {
    int levelSize = queue.size();
    for (int i = 0; i < levelSize; i++) {
        int node = queue.poll();
        // process all nodes at current level
    }
}

Multi-Source BFS

  • Add ALL sources to queue at start
  • Process simultaneously
  • Useful: Rotting Oranges, Distance to Nearest 1

Grid BFS Directions

int[][] dirs = {{-1,0}, {1,0}, {0,-1}, {0,1}};
// Add bounds checking: nr >= 0 && nr < rows && nc >= 0 && nc < cols

Complexity

  • Time: O(V + E)
  • Space: O(V)
  • Shortest path: O(V + E)