iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

DFS

Depth-First Search walks a graph or tree by going as deep as possible before backtracking. It’s the foundation of topological sort, cycle detection, connected components, maze solving, and most recursion problems on trees.

Recursive + iterative + applications

EXAMPLE
// 1) DFS on a tree (recursive)
class TreeNode {
    constructor(val, children = []) { this.val = val; this.children = children; }
}

function dfsTree(node, visit) {
    if (!node) return;
    visit(node);                                  // pre-order
    for (const child of node.children) dfsTree(child, visit);
    // post-order action here
}

// 2) DFS on a graph (with visited set)
function dfsGraph(graph, start, visit) {
    const visited = new Set();
    (function go(u) {
        if (visited.has(u)) return;
        visited.add(u);
        visit(u);
        for (const v of graph[u] ?? []) go(v);
    })(start);
    return visited;
}

const graph = {
    A: ['B', 'C'],
    B: ['D'],
    C: ['D', 'E'],
    D: [],
    E: ['F'],
    F: [],
};
dfsGraph(graph, 'A', console.log);          // A B D C E F

// 3) Iterative DFS (explicit stack — avoids recursion limits)
function dfsIterative(graph, start) {
    const visited = new Set();
    const stack   = [start];
    while (stack.length) {
        const u = stack.pop();
        if (visited.has(u)) continue;
        visited.add(u);
        for (const v of graph[u] ?? []) {
            if (!visited.has(v)) stack.push(v);
        }
    }
    return visited;
}

// 4) Cycle detection in a directed graph (white / grey / black)
function hasCycle(graph) {
    const WHITE = 0, GREY = 1, BLACK = 2;
    const color = new Map();
    for (const u of Object.keys(graph)) color.set(u, WHITE);

    function dfs(u) {
        color.set(u, GREY);
        for (const v of graph[u] ?? []) {
            if (color.get(v) === GREY) return true;       // back-edge → cycle
            if (color.get(v) === WHITE && dfs(v)) return true;
        }
        color.set(u, BLACK);
        return false;
    }

    return Object.keys(graph).some((u) => color.get(u) === WHITE && dfs(u));
}

// 5) Topological sort (post-order DFS, reversed)
function topoSort(graph) {
    const visited = new Set();
    const order   = [];
    function dfs(u) {
        if (visited.has(u)) return;
        visited.add(u);
        for (const v of graph[u] ?? []) dfs(v);
        order.push(u);                                  // record on the way out
    }
    Object.keys(graph).forEach(dfs);
    return order.reverse();
}

const deps = {
    bootstrap: ['styles', 'routes'],
    styles:    ['theme'],
    routes:    ['auth'],
    auth:      [],
    theme:     [],
};
topoSort(deps);              // ['bootstrap', 'styles', 'theme', 'routes', 'auth']

// 6) Connected components in an undirected graph
function components(graph) {
    const seen  = new Set();
    const comps = [];
    for (const u of Object.keys(graph)) {
        if (seen.has(u)) continue;
        const comp = [];
        (function go(x) {
            if (seen.has(x)) return;
            seen.add(x);
            comp.push(x);
            for (const v of graph[x]) go(v);
        })(u);
        comps.push(comp);
    }
    return comps;
}

// 7) Grid DFS — flood fill / island count
function numIslands(grid) {
    const R = grid.length, C = grid[0].length;
    let count = 0;

    function sink(r, c) {
        if (r < 0 || c < 0 || r >= R || c >= C || grid[r][c] !== 1) return;
        grid[r][c] = 2;                                  // mark visited
        sink(r + 1, c); sink(r - 1, c);
        sink(r, c + 1); sink(r, c - 1);
    }

    for (let r = 0; r < R; r++) {
        for (let c = 0; c < C; c++) {
            if (grid[r][c] === 1) { count++; sink(r, c); }
        }
    }
    return count;
}

// 8) Recursion-depth pitfall — convert to iterative for big inputs
// JS default stack ~10-15k frames; deep linked lists or paths blow it.
// Iterative + explicit stack scales linearly with heap.

// 9) Time / space complexity
//   • Tree:  O(n) time, O(h) stack space, h = height
//   • Graph: O(V + E) time, O(V) visited + O(V) recursion stack

// 10) DFS vs BFS — when to use which
//   DFS: existence questions, topological order, cycle detection, backtracking
//   BFS: shortest path on unweighted graphs, level-order, minimum steps

// 11) Common bugs
//   • Forgetting the visited set on a graph → infinite recursion on cycles
//   • Mutating the visited container while iterating outer loop
//   • Reverse-of-post-order: ALWAYS return order.reverse() for topo
//   • Off-by-one in grid bounds checks
//   • Recursing without a base case → stack overflow

Why it matters

Recursive DFS is concise but blows the stack on deep inputs — if you’re writing competitive code or processing user-controlled graph depth, write the iterative version with an explicit stack so you can’t crash on a 100k-node chain.

Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.

Example

Example
function dfs(u, graph, seen = new Set()) {
    if (seen.has(u)) return;
    seen.add(u);
    for (const v of graph.get(u) ?? []) dfs(v, graph, seen);
    return seen;
}
Try it Yourself »

Exercise

DFS uses a…

Discussion

Loading…