Pattern

DFS & BFS

The two fundamental graph and tree traversal strategies — go deep or go wide.

Roadmap

  1. What Are These Patterns?
  2. When to Use DFS
  3. When to Use BFS
  4. DFS on Trees
  5. BFS on Trees (Level Order)
  6. DFS on Graphs
  7. BFS on Graphs & Grids
  8. DFS vs BFS Comparison
  9. Common Problems
  10. Interactive Demo
  11. Complexity Analysis
  12. Key Takeaways
Step 01

What Are These Patterns?

Depth-First Search (DFS) explores as far as possible along each branch before backtracking. It uses a stack (or recursion, which is an implicit stack). Breadth-First Search (BFS) explores all neighbors at the current depth before moving to the next level. It uses a queue.

Same tree, two completely different traversal orders:

Same Tree, Different Strategies

The numbers on each node show the visit order. Notice how DFS dives deep immediately while BFS sweeps level by level.

DFS (Preorder)

1 B C D E F G 1 2 3 4 5 6 7
A → B → D → E → C → F → G
Uses stack / recursion

BFS (Level Order)

L0 L1 L2 A B C D E F G 1 2 3 4 5 6 7
A → B → C → D → E → F → G
Uses queue
💡

DFS is like exploring a maze by always taking the leftmost turn until you hit a dead end, then backtracking. BFS is like dropping a stone in water — the wavefront expands outward uniformly. Both visit every reachable node exactly once.

Step 02

When to Use DFS

DFS is your go-to when the problem involves exploring all possibilities, working with tree structures, or when path information matters more than distance.

Path finding in trees
Root-to-leaf paths, path sum, longest path. DFS naturally tracks the current path.
Cycle detection
Detect cycles in directed or undirected graphs using DFS coloring (white/gray/black).
Topological sort
Order tasks with dependencies. DFS postorder gives reverse topological order.
Exhaustive search / backtracking
Generate all permutations, combinations, subsets, or solve constraint problems.
Tree problems (most)
Max depth, diameter, validate BST, serialize/deserialize. Recursion maps naturally to DFS.
Connected components
Count islands, flood fill, find connected regions in a graph or grid.

Rule of thumb: If you need to explore an entire subtree or all paths from a node, DFS is usually simpler and more memory-efficient (stack depth = tree height, not tree width).

Step 03

When to Use BFS

BFS shines when you need the shortest path or minimum number of steps in unweighted graphs and grids.

Shortest path (unweighted)
BFS guarantees the first time you reach a node is via the shortest path. This is its killer feature.
Level-order traversal
Process tree nodes level by level. The classic "queue + size" pattern handles this elegantly.
Minimum steps / moves
Minimum knight moves, word ladder, rotting oranges. Each BFS layer = one time unit.
Nearest neighbor
Find the closest node with a given property. BFS finds it with minimal exploration.
🎯

The moment you see "shortest", "minimum steps", or "nearest" in an unweighted graph problem, reach for BFS. DFS might find a path, but it will not guarantee it is the shortest one.

Step 04

DFS on Trees

Depth-First

DFS on a binary tree comes in three flavors depending on when you process the current node relative to its children: preorder (before), inorder (between), and postorder (after).

Three Traversal Orders

Same tree, same DFS, but the processing happens at a different point in the recursion.

1 2 3 4 5 6 7
PREORDER
1 2 4 5 3 6 7
node → left → right
INORDER
4 2 5 1 6 3 7
left → node → right
POSTORDER
4 5 2 6 7 3 1
left → right → node

Recursive DFS Template

// Recursive DFS — all three orders in one structure
void dfs(TreeNode node) {
    if (node == null) return;

    // preorder: process node HERE (before children)

    dfs(node.left);

    // inorder: process node HERE (between children)

    dfs(node.right);

    // postorder: process node HERE (after children)
}

Iterative DFS Template (Preorder)

void dfsIterative(TreeNode root) {
    if (root == null) return;
    Deque<TreeNode> stack = new ArrayDeque<>();
    stack.push(root);

    while (!stack.isEmpty()) {
        TreeNode node = stack.pop();
        // process node

        if (node.right != null) stack.push(node.right); // right first!
        if (node.left != null) stack.push(node.left);  // so left is popped first
    }
}
📝

When to use which order? Preorder for serialization and copying trees. Inorder for BST problems (gives sorted order). Postorder for computing sizes, heights, or any bottom-up aggregation.

Step 05

BFS on Trees (Level Order)

Breadth-First

BFS on a tree processes nodes level by level. The key technique is the "queue + size" pattern: at each level, record the current queue size, then process exactly that many nodes. This cleanly separates levels.

Level-Order Traversal Visual

Level 0 Level 1 Level 2 3 9 20 4 8 15 7 Result: [[3], [9, 20], [4, 8, 15, 7]]

BFS Level-Order Template

List<List<Integer>> levelOrder(TreeNode root) {
    List<List<Integer>> result = new ArrayList<>();
    if (root == null) return result;

    Queue<TreeNode> queue = new LinkedList<>();
    queue.offer(root);

    while (!queue.isEmpty()) {
        int size = queue.size();           // nodes at this level
        List<Integer> level = new ArrayList<>();

        for (int i = 0; i < size; i++) {
            TreeNode node = queue.poll();
            level.add(node.val);         // process node

            if (node.left != null)  queue.offer(node.left);
            if (node.right != null) queue.offer(node.right);
        }
        result.add(level);
    }
    return result;
}
💡

The int size = queue.size() line is critical. Without it, you cannot tell where one level ends and the next begins. This one-line trick enables level-aware BFS and is used in nearly every BFS tree problem.

Step 06

DFS on Graphs

Graph DFS

Unlike trees, graphs can have cycles. The critical difference: you must maintain a visited set to avoid infinite loops. The structure is otherwise identical to tree DFS.

Graph DFS with Visited Coloring

Starting from node 0, DFS explores deep before backtracking. Gray = currently in stack. Black = fully processed.

0 1 1 2 2 3 5 4 4 5 3 6 Visit order: 0 → 1 → 2 → 5 → 4 → 3

Template: Adjacency List

// DFS on a graph with adjacency list
void dfs(int node, List<List<Integer>> graph, boolean[] visited) {
    visited[node] = true;
    // process node

    for (int neighbor : graph.get(node)) {
        if (!visited[neighbor]) {
            dfs(neighbor, graph, visited);
        }
    }
}

Template: Grid (2D Matrix)

// DFS on a grid — e.g., Number of Islands
int[][] dirs = {{0,1},{0,-1},{1,0},{-1,0}};

void dfs(char[][] grid, int r, int c) {
    if (r < 0 || r >= grid.length ||
        c < 0 || c >= grid[0].length ||
        grid[r][c] != '1') return;

    grid[r][c] = '0';                // mark visited

    for (int[] d : dirs) {
        dfs(grid, r + d[0], c + d[1]);
    }
}

Visited tracking options: (1) A separate boolean[] or Set. (2) Modify the input in-place (like marking '1' as '0' in grids). (3) Three-state coloring (white/gray/black) for cycle detection in directed graphs.

Step 07

BFS on Graphs & Grids

Graph/Grid BFS

BFS on grids is one of the most common interview patterns. The wavefront expands outward from the source, guaranteeing the shortest path in unweighted grids.

BFS Wavefront on a Grid

Starting from S, BFS explores all cells at distance 1, then distance 2, etc. Each ring of numbers represents one BFS layer.

S
1
2
3
4
#
1
#
#
4
5
#
2
3
4
5
6
7
8
3
#
5
#
#
8
9
4
5
6
7
#
9
E
Shortest path from S to E = 10 steps
Start
Visited
Path
Wall

Template: Shortest Path in Grid

// BFS shortest path on a grid
int[][] dirs = {{0,1},{0,-1},{1,0},{-1,0}};

int shortestPath(int[][] grid, int[] start, int[] end) {
    int rows = grid.length, cols = grid[0].length;
    boolean[][] visited = new boolean[rows][cols];
    Queue<int[]> queue = new LinkedList<>();

    queue.offer(start);
    visited[start[0]][start[1]] = true;
    int steps = 0;

    while (!queue.isEmpty()) {
        int size = queue.size();           // all nodes at this distance

        for (int i = 0; i < size; i++) {
            int[] pos = queue.poll();
            if (pos[0] == end[0] && pos[1] == end[1])
                return steps;               // found shortest path!

            for (int[] d : dirs) {
                int nr = pos[0] + d[0], nc = pos[1] + d[1];
                if (nr >= 0 && nr < rows &&
                    nc >= 0 && nc < cols &&
                    !visited[nr][nc] &&
                    grid[nr][nc] != 1) {       // not a wall
                    visited[nr][nc] = true;
                    queue.offer(new int[]{nr, nc});
                }
            }
        }
        steps++;
    }
    return -1;  // unreachable
}
🎯

Mark visited when enqueuing, not when dequeuing. This is a common mistake. If you wait to mark visited until you poll, you may add the same cell to the queue multiple times from different neighbors, wasting time and space.

Step 08

DFS vs BFS Comparison

Aspect DFS BFS
Data Structure Stack (or recursion) Queue
Traversal Order Deepest nodes first Nearest nodes first
Shortest Path? No guarantee Yes (unweighted)
Space (Tree) O(h) — height O(w) — max width
Space (Graph) O(V) O(V)
Best For Paths, cycles, backtracking, exhaustive search Shortest path, level order, minimum steps
Implementation Simpler (recursion) Always iterative
Risk Stack overflow on deep graphs High memory on wide graphs
📝

For balanced binary trees, DFS uses O(log n) space while BFS uses O(n) space (the last level has n/2 nodes). For extremely deep, narrow graphs (like a linked list), BFS uses O(1) space while DFS uses O(n). Choose based on the shape of your data.

Step 09

Common Problems

Classic LeetCode problems organized by which traversal strategy to use. Many problems can be solved with either DFS or BFS, but one is usually cleaner.

#104 Maximum Depth of Binary Tree DFS Easy
#200 Number of Islands DFS/BFS Medium
#102 Binary Tree Level Order Traversal BFS Medium
#133 Clone Graph DFS/BFS Medium
#207 Course Schedule DFS Medium
#994 Rotting Oranges BFS Medium
💡

Practice tip: Start with #104 (pure recursive DFS) and #102 (pure BFS template). Then tackle #200 both ways — DFS with flood fill and BFS with a queue. Once comfortable, move to #207 for topological sort and #994 for multi-source BFS.

Step 10

Interactive Demo

Step through both traversal strategies to see exactly how each one explores nodes differently.

⚙ Demo 1: DFS Tree Traversal

Click "Step" to watch DFS (preorder) explore a binary tree. Nodes are colored as they are visited.

Stack: []
1 2 3 4 5 6 7
Press "Step →" or "Run All" to begin DFS traversal.

⚙ Demo 2: BFS Grid Pathfinding

Watch BFS find the shortest path from S to E. The wavefront expands outward layer by layer.

Queue size: 0 Steps: 0
Press "Step →" or "Run All" to begin BFS pathfinding.
Step 11

Complexity Analysis

Time
O(V + E)
Space
O(V)

Why O(V + E)?

Both DFS and BFS visit every vertex exactly once — that is O(V). For each vertex, they examine all its edges — summed across all vertices, that is O(E). Total: O(V + E).

On a grid (R rows, C cols): V = R * C and each cell has at most 4 edges, so E = O(V). Time becomes O(R * C).

Space: The visited set takes O(V). DFS additionally uses O(V) stack space in the worst case (linear graph). BFS uses O(V) queue space in the worst case (all nodes at same level). Either way: O(V).

Space Differences on Trees

DFS SPACE
O(h) = O(log n) balanced
Stack holds at most the height of the tree. Great for balanced trees.
BFS SPACE
O(w) = O(n) balanced
Queue holds the widest level. For a complete tree, the last level has n/2 nodes.
Step 12

Key Takeaways

01

DFS = stack, BFS = queue. This is the single most important distinction. DFS goes deep using LIFO, BFS goes wide using FIFO. Everything else follows from this.

02

BFS guarantees shortest path in unweighted graphs. The first time BFS reaches a node, it has found the minimum number of edges. DFS cannot make this guarantee.

03

Always track visited nodes in graphs. Trees are acyclic by definition, so you do not need a visited set. Graphs can have cycles, so forgetting the visited set means infinite loops.

04

DFS maps naturally to recursion. Most tree problems are cleanest with recursive DFS. But watch the stack depth — for very deep graphs (over 10,000 levels), convert to iterative DFS to avoid stack overflow.

05

The "queue + size" pattern is BFS's most important technique. Capturing queue.size() at the start of each level lets you process nodes level by level. This powers level-order traversal, shortest path counting, and multi-source BFS.

06

Many problems can use either — pick the simpler one. Number of Islands works with both DFS and BFS. Clone Graph works with both. Default to DFS for tree problems and BFS for shortest-path problems, then choose the cleaner option for everything else.