BFS vs DFS: When to Use Each with Examples

BFS and DFS are the two foundational graph traversal algorithms. Every other graph algorithm — Dijkstra, Bellman-Ford, topological sort, cycle detection, connected components — is built on one of them. Knowing not just how they work but when to choose each is what separates a passing answer from a strong one.

The Core Difference

BFS (Breadth-First Search) explores all neighbors at the current depth before going deeper. It uses a queue. It finds the shortest path in unweighted graphs.

DFS (Depth-First Search) goes as deep as possible down each branch before backtracking. It uses a stack (or the call stack via recursion). It’s the foundation for topological sort, cycle detection, and connected components.

BFS Implementation

from collections import deque

def bfs(graph: dict, start: int) -> list:
    """
    Breadth-first search returning nodes in order visited.
    graph: adjacency list {node: [neighbors]}
    """
    visited = set()
    queue = deque([start])
    visited.add(start)
    order = []

    while queue:
        node = queue.popleft()
        order.append(node)

        for neighbor in graph[node]:
            if neighbor not in visited:
                visited.add(neighbor)
                queue.append(neighbor)

    return order

def bfs_shortest_path(graph: dict, start: int, end: int) -> list | None:
    """
    Find shortest path between start and end in an unweighted graph.
    Returns the path as a list, or None if no path exists.
    Time: O(V + E), Space: O(V)
    """
    if start == end:
        return [start]

    visited = {start}
    # Store (current_node, path_to_current_node)
    queue = deque([(start, [start])])

    while queue:
        node, path = queue.popleft()

        for neighbor in graph[node]:
            if neighbor == end:
                return path + [neighbor]
            if neighbor not in visited:
                visited.add(neighbor)
                queue.append((neighbor, path + [neighbor]))

    return None  # No path exists

# Example
graph = {
    0: [1, 2],
    1: [0, 3, 4],
    2: [0, 4],
    3: [1],
    4: [1, 2]
}
print(bfs_shortest_path(graph, 0, 3))  # [0, 1, 3]
print(bfs_shortest_path(graph, 3, 4))  # [3, 1, 4]

DFS Implementation

def dfs_recursive(graph: dict, node: int, visited: set = None) -> list:
    """
    Recursive DFS. Returns nodes in order visited.
    Time: O(V + E), Space: O(V) for recursion stack
    """
    if visited is None:
        visited = set()

    visited.add(node)
    result = [node]

    for neighbor in graph[node]:
        if neighbor not in visited:
            result.extend(dfs_recursive(graph, neighbor, visited))

    return result

def dfs_iterative(graph: dict, start: int) -> list:
    """
    Iterative DFS using explicit stack.
    Use this for large graphs to avoid recursion limit (Python default: 1000).
    """
    visited = set()
    stack = [start]
    order = []

    while stack:
        node = stack.pop()  # LIFO — this is what makes it DFS vs BFS
        if node in visited:
            continue
        visited.add(node)
        order.append(node)

        # Push neighbors in reverse order to maintain left-to-right traversal
        for neighbor in reversed(graph[node]):
            if neighbor not in visited:
                stack.append(neighbor)

    return order

When to Use BFS vs DFS

Scenario Use Why
Shortest path in unweighted graph BFS BFS explores by increasing distance; first time you reach a node is via shortest path
Cycle detection DFS Back edges (reaching a node in current DFS path) indicate cycles
Topological sort DFS Post-order DFS naturally produces reverse topological order
Connected components Either Both explore all reachable nodes; DFS slightly simpler with recursion
Maze solving DFS (find any path), BFS (find shortest path) Depends on requirement
Level-order traversal (trees) BFS Each BFS level = one tree level
Detect if path exists Either DFS simpler; BFS if you also need the path
Very deep graphs (10^6 depth) BFS or iterative DFS Recursive DFS hits Python recursion limit at ~1000
Wide graphs (high branching factor) DFS BFS memory usage = O(branching_factor ^ depth); can be huge

Complexity Analysis

Both BFS and DFS are O(V + E) time and O(V) space where V = vertices and E = edges.

Space distinction:

  • BFS: queue holds the current frontier — worst case O(V) for a wide tree
  • DFS: recursion stack depth = longest path — O(V) worst case for a linear chain, O(log V) for a balanced tree

Common Interview Patterns Using BFS

Grid BFS (Number of Islands, Rotten Oranges, Minimum Steps)

def bfs_grid(grid: list[list[int]], start_r: int, start_c: int) -> int:
    """
    BFS on a 2D grid. Returns number of steps to explore all reachable cells.
    Template for: minimum steps problems, multi-source BFS, level-by-level processing.
    """
    rows, cols = len(grid), len(grid[0])
    directions = [(0, 1), (0, -1), (1, 0), (-1, 0)]  # Right, left, down, up
    visited = set([(start_r, start_c)])
    queue = deque([(start_r, start_c, 0)])  # (row, col, steps)

    while queue:
        r, c, steps = queue.popleft()

        for dr, dc in directions:
            nr, nc = r + dr, c + dc
            if (0 <= nr < rows and 0 <= nc < cols
                    and (nr, nc) not in visited
                    and grid[nr][nc] != 0):  # 0 = wall/obstacle
                visited.add((nr, nc))
                queue.append((nr, nc, steps + 1))

    return len(visited)

Common Interview Patterns Using DFS

Path Problems

def has_path_with_constraint(graph: dict, start: int, end: int,
                              constraint: callable) -> bool:
    """
    DFS to find if a path exists where all nodes satisfy constraint.
    Template for: word ladder variants, path sum in trees.
    """
    def dfs(node, visited):
        if node == end:
            return True
        visited.add(node)
        for neighbor in graph[node]:
            if neighbor not in visited and constraint(neighbor):
                if dfs(neighbor, visited):
                    return True
        return False

    return dfs(start, {start})

Multi-Source BFS

Start BFS from multiple sources simultaneously. Classic example: Rotten Oranges — find minimum minutes to rot all fresh oranges, where all rotten oranges spread simultaneously.

def multi_source_bfs(grid: list[list[int]]) -> int:
    """
    0 = empty, 1 = fresh orange, 2 = rotten orange.
    Find minimum minutes until all oranges rot.
    """
    rows, cols = len(grid), len(grid[0])
    queue = deque()
    fresh_count = 0

    # Initialize: add ALL rotten oranges to queue at time=0
    for r in range(rows):
        for c in range(cols):
            if grid[r][c] == 2:
                queue.append((r, c, 0))
            elif grid[r][c] == 1:
                fresh_count += 1

    if fresh_count == 0:
        return 0

    directions = [(0, 1), (0, -1), (1, 0), (-1, 0)]
    minutes = 0

    while queue:
        r, c, time = queue.popleft()
        for dr, dc in directions:
            nr, nc = r + dr, c + dc
            if 0 <= nr < rows and 0 <= nc < cols and grid[nr][nc] == 1:
                grid[nr][nc] = 2
                fresh_count -= 1
                minutes = time + 1
                queue.append((nr, nc, time + 1))

    return minutes if fresh_count == 0 else -1

Practice Problems

BFS:

  • LeetCode 102: Binary Tree Level Order Traversal
  • LeetCode 127: Word Ladder (BFS on implicit graph)
  • LeetCode 994: Rotting Oranges (multi-source BFS)
  • LeetCode 286: Walls and Gates (multi-source BFS)

DFS:

  • LeetCode 200: Number of Islands (DFS flood fill)
  • LeetCode 133: Clone a Graph
  • LeetCode 207: Course Schedule (cycle detection)
  • LeetCode 417: Pacific Atlantic Water Flow

Related Graph Topics

  • Number of Islands: Flood Fill Pattern — the most common BFS/DFS interview problem; master this template and you can solve 20+ grid variants
  • Detect a Cycle in a Graph — DFS with parent tracking (undirected) and three-color marking (directed) both extend the basic DFS traversal covered here
  • Topological Sort — DFS post-order is one of the two approaches for topological sort; Kahn’s algorithm is BFS-based
Scroll to Top