← System Design Simulator

DFS Visualization for Coding Interviews

By Rahul Kumar · Senior Software Engineer · Updated · Category: Graphs

Recursive explore; discovery/finish time coloring.

Concept

Depth-First Search dives as far as it can down one branch before backtracking to try the next. That single behavioral difference from BFS unlocks a completely different class of problems: cycle detection, topological ordering, strongly connected components, bridges and articulation points, and any tree DP that needs post-order aggregation. The elegance of DFS is that it gives you two natural time coordinates per vertex - a discovery time the moment you first step onto it, and a finish time the moment you back out of its subtree. Those timestamps expose the tree structure of the traversal and let you classify every edge as tree, back, forward, or cross. Whenever an algorithm claims to work in linear time on a graph and does something structural (ordering, component finding, reachability, ancestry), DFS is almost certainly underneath. Runtime is O(V+E) on an adjacency list, space is O(V) for the recursion stack or an explicit stack and two timestamp arrays.

DFS — Interactive Simulator

Runs fully client-side in your browser; no sign-up. Or open full screen →

Launch the interactive DFS visualization — animated algorithm, step-through, and live data-structure updates.

How it works

Maintain a global clock that ticks each time you enter or exit a vertex. On visit(u), bump the clock and stamp disc[u]. For each neighbor v, if v is unvisited, recurse into it; that recursive call produces the tree edge (u, v). When recursion returns from every child, bump the clock again and stamp finish[u]. Two lemmas make timestamps powerful. First, the parenthesis theorem: for any two vertices u and v, the intervals [disc[u], finish[u]] and [disc[v], finish[v]] are either disjoint or one fully contains the other; they never partially overlap. Second, u is an ancestor of v in the DFS tree if and only if [disc[u], finish[u]] contains [disc[v], finish[v]]. Together these give you O(1) ancestor checks and make edge classification trivial: a back edge points to an ancestor (disc[v] < disc[u] and finish[v] unset), a forward edge points into your own subtree (disc[v] > disc[u] and finish[v] set), a cross edge points elsewhere (finish[v] set and disc[v] < disc[u]). Reversed finish order is a topological sort on a DAG. For an iterative version, simulate the call stack with an explicit Deque of (vertex, neighborIterator) pairs to avoid blowing the JVM stack on deep graphs.

Implementation

Recursive DFS with discovery and finish timestamps
import java.util.*;

public class DFS {
    private final List<List<Integer>> adj;
    private final int[] disc;
    private final int[] finish;
    private int clock;

    public DFS(List<List<Integer>> adj) {
        this.adj = adj;
        int n = adj.size();
        this.disc = new int[n];
        this.finish = new int[n];
        Arrays.fill(disc, -1);
        Arrays.fill(finish, -1);
        this.clock = 0;
    }

    /** Run DFS from src and fill disc[]/finish[] for the reachable component. */
    public void dfs(int src) {
        visit(src);
    }

    private void visit(int u) {
        disc[u] = clock++;
        for (int v : adj.get(u)) {
            if (disc[v] == -1) visit(v);           // tree edge
            // else v is either ancestor (back), descendant (forward), or cross edge
        }
        finish[u] = clock++;
    }

    /** Full DFS across every component, returning reverse-finish order. */
    public int[] dfsAll() {
        int n = adj.size();
        for (int s = 0; s < n; s++) if (disc[s] == -1) visit(s);
        Integer[] order = new Integer[n];
        for (int i = 0; i < n; i++) order[i] = i;
        Arrays.sort(order, (a, b) -> Integer.compare(finish[b], finish[a]));
        int[] out = new int[n];
        for (int i = 0; i < n; i++) out[i] = order[i];
        return out;
    }

    public int[] discovery() { return disc; }
    public int[] finishTimes() { return finish; }

    /** u is an ancestor of v in the DFS tree iff disc[u] <= disc[v] and finish[v] <= finish[u]. */
    public boolean isAncestor(int u, int v) {
        return disc[u] <= disc[v] && finish[v] <= finish[u];
    }
}

Complexity

Common pitfalls

Key design decisions & trade-offs

Practice problems

Interview follow-ups

Why it matters

DFS is a core part of the Graphs toolkit. If you are preparing for a technical interview at a top-tier company or teaching a course, a working visualization is the fastest path from "I read about it" to "I understand it".

Related