Knowledge Guide
HomeDSAGraphs

Graph Traversal - Depth First Search(DFS)

Depth-First Search dives down one path as far as edges allow, using a stack (explicit or the call stack) to remember where to backtrack to, so it commits to a branch before ever looking sideways — that single choice is what separates it from BFS.

Recognize the pattern

Brute force → optimal

Brute force: no adjacency list — rescan the full edge list to find each node's neighbors. That's O(V) per lookup, O(V·(V+E)) overall, and without a visited set it loops forever on any cycle.

Optimal: adjacency list (O(1)-amortized neighbor iteration) plus a visited array so every vertex is entered once and every edge is scanned once. That's the version below.

Complexity, derived from first principles

Time. Each vertex is pushed/recursed into and marked visited exactly once → V unit operations. Each vertex's adjacency list is scanned exactly once when that vertex is processed; summed across all vertices this totals the sum of adjacency-list lengths — 2E for an undirected graph (each edge sits in two lists) or E for a directed graph. Total work = O(V) + O(E) = O(V + E).

Space. visited[]: O(V). Call stack (or explicit stack): worst case equals the longest simple path from the start, up to O(V) for a skewed/path-shaped graph. Adjacency list itself: O(V + E). Overall auxiliary space beyond the input graph: O(V).

Traced worked example

Graph (undirected), adjacency lists: A:[B,C], B:[A,D], C:[A,D], D:[B,C,E], E:[D]. Start at A, recursive DFS, neighbors visited in list order. Only 4 of the 5 edges are ever followed (become tree edges); edge A-C is checked but never followed because C is already visited by the time A looks at it.

Call / actionvisited so farstack (call frames, top last)
dfs(A)A[A]
A→B unvisited, follow edge A-B, dfs(B)A,B[A,B]
B→A visited, skip; B→D unvisited, follow edge B-D, dfs(D)A,B,D[A,B,D]
D→B visited, skip; D→C unvisited, follow edge D-C, dfs(C)A,B,D,C[A,B,D,C]
C→A visited, C→D visited → return from CA,B,D,C[A,B,D]
D→E unvisited, follow edge D-E, dfs(E)A,B,D,C,E[A,B,D,E]
E→D visited → return from E; D exhausted → return from D; B exhausted → return from BA,B,D,C,E[A]
Back in A: A→C is visited (back-edge, checked but not followed) → A exhausted → returnA,B,D,C,E[]

Visit order: A, B, D, C, E. Every one of the 5 edges was inspected exactly twice (10 = 2E checks), but only 4 edges (A-B, B-D, D-C, D-E) were ever followed — the 5th inspection, A-C, is a back-edge to an already-visited node and contributes to the O(V+E) check count without adding a tree edge. Every vertex entered once — matches O(V+E).

Step through it yourself

The debugger below runs DFS on a small undirected graph with cycles. Watch the call stack grow as it dives deep, nodes turn green as they're visited, and — the crux — at every edge you predict whether the neighbour is already visited (skip) or new (recurse). The skips are exactly the cycle edges the visited set saves you from.

Code — recursive and iterative

import java.util.*;

public class DFS {
    // Recursive DFS
    static void dfs(int node, List> adj, boolean[] visited, List order) {
        visited[node] = true;
        order.add(node);
        for (int next : adj.get(node)) {
            if (!visited[next]) dfs(next, adj, visited, order);
        }
    }

    // Iterative DFS with an explicit stack
    static List dfsIterative(int start, List> adj, int n) {
        boolean[] visited = new boolean[n];
        List order = new ArrayList<>();
        Deque stack = new ArrayDeque<>();
        stack.push(start);
        while (!stack.isEmpty()) {
            int node = stack.pop();
            if (visited[node]) continue; // may be pushed multiple times
            visited[node] = true;
            order.add(node);
            List neighbors = adj.get(node);
            for (int i = neighbors.size() - 1; i >= 0; i--) {
                int next = neighbors.get(i);
                if (!visited[next]) stack.push(next);
            }
        }
        return order;
    }
}

Note the iterative version checks visited on pop, not on push, because a node can be pushed by more than one neighbor before it is ever processed — skipping that check double-processes nodes and breaks the O(V+E) bound.

Pitfalls

When to use / when not — vs BFS

Use DFS for: cycle detection, topological sort, connected components, exhaustive/backtracking search (permutations, maze solving), computing tree properties (heights, articulation points) — anywhere the recursive 'go deep, then back out' structure matches the problem, and where its O(V) space (vs frontier width) is the smaller footprint.

Use BFS instead when you need the shortest path / minimum number of edges in an unweighted graph, or level-order processing — DFS can find a path but gives no shortest-path guarantee, and it may explore a much longer path first, wasting time on problems where proximity matters.

Trade-off summary: DFS space is bounded by the longest path (good for deep, narrow graphs); BFS space is bounded by the widest frontier (good for shortest-path guarantees but can blow up on wide graphs).

Takeaways

Recall: Why must the iterative stack-based DFS re-check visited after popping a node, even though it also checked before pushing?


Derived from graph theory fundamentals (Cormen et al., Introduction to Algorithms) and standard DFS traversal treatments.

🤖 Don't fully get this? Learn it with Claude

Stuck on Graph Traversal - Depth First Search(DFS)? Open Claude, copy a block below, and it'll teach you this exact concept — visually and interactively.

🎨 Explain it visually

Build the mental picture, not memorization.

I just read a lesson on **Graph Traversal - Depth First Search(DFS)** (DSA) and want to truly understand it. Explain Graph Traversal - Depth First Search(DFS) from first principles using ONE vivid real-world analogy and a visual mental model — draw it as ASCII art or a clear step-by-step diagram — with a concrete example using real numbers. Then ask me one question to check I got the mental picture, and wait for my reply. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🤔 Walk me through it (interactive)

Socratic — adapts to where you're stuck.

Teach me **Graph Traversal - Depth First Search(DFS)** interactively. Ask me ONE guiding question at a time, wait for my answer, and adapt to my confusion — build the idea with me step by step instead of explaining it all at once. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🧪 Quiz me & fix my gaps

Active recall exposes what you missed.

Quiz me on **Graph Traversal - Depth First Search(DFS)** with 5 questions, easy to tricky, ONE at a time. Tell me if each answer is right; at the end, explain clearly what I got wrong and why. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🧠 Make it stick

Intuition + hook + flashcards for long-term memory.

Help me remember **Graph Traversal - Depth First Search(DFS)** for the long term: give the one-sentence intuition, a memorable hook/mnemonic, a tiny worked example, and 3 active-recall flashcards (Q -> A). If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.

📝 My notes