Home/Learn/BFS vs DFS — When to Use Each

Pattern Guide

BFS vs DFS — When to Use Each

"Shortest path → BFS. Connected components → DFS. Know which in 10 seconds."

BFS and DFS are the two fundamental traversal strategies. Learn precisely when each applies, the key code templates, and how to recognize which one a problem needs from its constraints and output type.

Problems you can solve with this pattern

8 problems · click any to start solving

All graph
1Rotting OrangesMediumSolve
2Number of IslandsMediumSolve
3Pacific Atlantic Water FlowMediumSolve
4Course Schedule (Topological Sort)MediumSolve
BFS — shortest path, level-order, multi-source
// Single-source BFS
function bfs(graph, start) {
    const visited = new Set([start]);
    let queue = [start];
    let level = 0;
    while (queue.length) {
        const nextQueue = [];
        for (const node of queue) {
            // process node at distance 'level'
            for (const neighbor of graph[node]) {
                if (!visited.has(neighbor)) {
                    visited.add(neighbor);
                    nextQueue.push(neighbor);
                }
            }
        }
        queue = nextQueue;
        level++;
    }
}

// Multi-source BFS (start from multiple nodes simultaneously)
// Used for: 01 Matrix, Rotting Oranges, walls-and-gates
function multiSourceBFS(grid, sources) {
    const dist = Array.from(grid, r => new Array(r.length).fill(Infinity));
    let queue = [];
    for (const [r, c] of sources) { dist[r][c] = 0; queue.push([r, c]); }
    const dirs = [[0,1],[0,-1],[1,0],[-1,0]];
    while (queue.length) {
        const [r, c] = queue.shift();
        for (const [dr, dc] of dirs) {
            const nr = r+dr, nc = c+dc;
            if (nr>=0 && nr<grid.length && nc>=0 && nc<grid[0].length
                && dist[nr][nc]===Infinity) {
                dist[nr][nc] = dist[r][c] + 1;
                queue.push([nr, nc]);
            }
        }
    }
    return dist;
}

BFS and DFS both visit every node in a graph/tree, but they answer different questions. BFS finds the shortest path in unweighted graphs. DFS finds connected components, detects cycles, computes topological order, and enables backtracking. Choosing the wrong one either gives a wrong answer or TLEs.

QuestionUseWhy
Shortest path (unweighted)BFSExplores level-by-level — first time you reach a node is the shortest
Connected componentsDFSFlood-fill: mark all reachable nodes from a starting point
Cycle detectionDFSTrack state: unvisited → visiting → visited (3-color)
Topological sortDFS (Kahn's BFS)DFS finish time ordering; or BFS on indegree-0 nodes
Level-order outputBFSEach queue flush = one level
Backtracking / all pathsDFSRecursion with undo naturally models path exploration
Grid: min steps to reach targetBFSEach step costs 1 — BFS gives shortest automatically
Grid: flood fill / connected region sizeDFSSimpler recursive mark-and-expand
SCC (strongly connected)DFS × 2Kosaraju: DFS finish order + DFS on reverse graph

BFS Template (Level-Aware)

BFS — shortest path, level-order, multi-source
// Single-source BFS
function bfs(graph, start) {
    const visited = new Set([start]);
    let queue = [start];
    let level = 0;
    while (queue.length) {
        const nextQueue = [];
        for (const node of queue) {
            // process node at distance 'level'
            for (const neighbor of graph[node]) {
                if (!visited.has(neighbor)) {
                    visited.add(neighbor);
                    nextQueue.push(neighbor);
                }
            }
        }
        queue = nextQueue;
        level++;
    }
}

// Multi-source BFS (start from multiple nodes simultaneously)
// Used for: 01 Matrix, Rotting Oranges, walls-and-gates
function multiSourceBFS(grid, sources) {
    const dist = Array.from(grid, r => new Array(r.length).fill(Infinity));
    let queue = [];
    for (const [r, c] of sources) { dist[r][c] = 0; queue.push([r, c]); }
    const dirs = [[0,1],[0,-1],[1,0],[-1,0]];
    while (queue.length) {
        const [r, c] = queue.shift();
        for (const [dr, dc] of dirs) {
            const nr = r+dr, nc = c+dc;
            if (nr>=0 && nr<grid.length && nc>=0 && nc<grid[0].length
                && dist[nr][nc]===Infinity) {
                dist[nr][nc] = dist[r][c] + 1;
                queue.push([nr, nc]);
            }
        }
    }
    return dist;
}

DFS Template (With State)

DFS — cycle detection, components, topo sort
// Iterative DFS (avoids stack overflow on large graphs)
function dfsIterative(graph, start) {
    const visited = new Set();
    const stack = [start];
    while (stack.length) {
        const node = stack.pop();
        if (visited.has(node)) continue;
        visited.add(node);
        for (const neighbor of graph[node])
            if (!visited.has(neighbor)) stack.push(neighbor);
    }
}

// Recursive DFS with 3-color cycle detection
// WHITE=0 (unvisited), GRAY=1 (in current path), BLACK=2 (done)
function hasCycle(graph, n) {
    const color = new Array(n).fill(0);
    const dfs = (node) => {
        color[node] = 1; // GRAY — currently visiting
        for (const neighbor of graph[node]) {
            if (color[neighbor] === 1) return true;  // back edge = cycle
            if (color[neighbor] === 0 && dfs(neighbor)) return true;
        }
        color[node] = 2; // BLACK — fully processed
        return false;
    };
    for (let i = 0; i < n; i++)
        if (color[i] === 0 && dfs(i)) return true;
    return false;
}

// DFS postorder = reverse topological sort
function topoSort(graph, n) {
    const visited = new Set(), order = [];
    const dfs = (node) => {
        visited.add(node);
        for (const nb of graph[node]) if (!visited.has(nb)) dfs(nb);
        order.push(node); // push AFTER all descendants
    };
    for (let i = 0; i < n; i++) if (!visited.has(i)) dfs(i);
    return order.reverse();
}

Key Pattern: 0-1 BFS (Two-Queue / Deque)

0-1 BFS: Edge weights are only 0 or 1. Use a deque: push weight-0 edges to the front, weight-1 edges to the back. Gives O(V+E) shortest path — faster than Dijkstra's O((V+E) log V) for this case.

Used when: moving in a direction costs 0 (free move, teleport) or 1 (step), and you want minimum cost.
0-1 BFS with deque
function zerOneBFS(graph, start, n) {
    const dist = new Array(n).fill(Infinity);
    dist[start] = 0;
    const deque = [start]; // front = cost 0, back = cost 1
    while (deque.length) {
        const u = deque.shift();
        for (const [v, w] of graph[u]) {
            if (dist[u] + w < dist[v]) {
                dist[v] = dist[u] + w;
                if (w === 0) deque.unshift(v); // free edge → front
                else deque.push(v);             // cost-1 edge → back
            }
        }
    }
    return dist;
}
The 30-second BFS vs DFS decision:
1. "Minimum steps / shortest path" → BFS (never DFS for shortest path in unweighted graph)
2. "Can reach / connected / flood fill" → DFS (simpler code)
3. "All cells within distance k" → BFS level-by-level
4. "Cycle detection / dependency order" → DFS with colors
5. "Start from multiple sources" → Multi-source BFS
6. "Binary edge weights (0 or 1)" → 0-1 BFS (deque)
7. "State space search (lock, word ladder)" → BFS (states as nodes, transitions as edges)