Pattern #26
Graph DFS
EssentialConnected components, flood fill, cycle detection, paths.
When to use
Use when you need to explore connectivity, count components, detect cycles, or enumerate all reachable nodes in a graph (not a tree).
Recognition cues
- Connected components / flood fill
- Detect cycles in a graph
- Reachability / all paths between nodes
- Topological sort or bipartite coloring
Common pitfalls
- Forgetting a visited set (infinite loop or double-count)
- Confusing graph DFS with tree DFS (no parent guarantees)
- Stack overflow on deep recursion in some languages
90-second recognition drill
Which pattern fits best?
- Connected components / flood fill
- Detect cycles in a graph
- Reachability / all paths between nodes
Interactive
Mental model
A full worked walkthrough of the invariant. Pause, scrub the dots, or use ← →. Aim to narrate each step yourself.
visited = {} · components = 0
Graph DFS is tree DFS plus a visited set. Two components: A–B–C and D–E.
How to think about it
Graph DFS is tree DFS with a visited set. Because a graph has no parent/child guarantees, you must mark a node before (or as) you recurse so you never revisit it. The whole skeleton is: mark, explore each unvisited neighbor, recurse. That is it. Almost every graph-DFS problem adds a layer on top of that invariant (count components, track a path, color bipartite, detect a back-edge).
Two representations dominate interview problems: an adjacency list (Map<V, V[]>) for general graphs, and a 2D grid (int[][] with 4-directional neighbors) for flood-fill / island problems. The template is identical; only the neighbor enumeration changes.
Template shapes
| Shape | Core move | Example |
|---|---|---|
| Count components | if !visited: dfs(v); count++ |
LC 200, LC 547 |
| Flood fill | Mutate cell, dfs 4-neighbors | LC 733, LC 695 |
| Clone graph | Map<old, new>; dfs + copy on visit |
LC 133 |
| Pacific-Atlantic | Two DFS from borders, intersect reach | LC 417 |
Complexity baseline
O(V + E) time, every vertex and edge is considered once thanks to visited. Space O(V) for the visited set plus recursion depth (worst case O(V) on a path graph). For grid problems replace V with cells (RC) and E with 4R*C.
From template to problem
- Choose the representation: adjacency list, grid, or implicit (e.g. words).
- Decide what “visited” means, a boolean array, a set, or mutating the grid in place.
- Identify the invariant each DFS call returns: a count, a boolean reach, a clone.
- Loop over all starts; each unvisited start seeds one component / one fill.
Template
Same skeleton in TypeScript, Python, and C#. Adapt the invariant; keep the structure.
/** Graph DFS template: count components + flood fill on a grid. */
export function countComponents(n: number, edges: [number, number][]): number {
const adj: number[][] = Array.from({ length: n }, () => []);
for (const [a, b] of edges) { adj[a].push(b); adj[b].push(a); }
const visited = new Array<boolean>(n).fill(false);
let count = 0;
for (let v = 0; v < n; v++) {
if (!visited[v]) { dfs(v); count++; }
}
return count;
function dfs(v: number) {
visited[v] = true;
for (const u of adj[v]) if (!visited[u]) dfs(u);
}
}
export function floodFill(
image: number[][], sr: number, sc: number, newColor: number,
): number[][] {
const orig = image[sr]![sc]!;
if (orig === newColor) return image;
const rows = image.length, cols = image[0]!.length;
const stack: [number, number][] = [[sr, sc]];
while (stack.length) {
const [r, c] = stack.pop()!;
if (r < 0 || c < 0 || r >= rows || c >= cols || image[r]![c] !== orig) continue;
image[r]![c] = newColor;
stack.push([r + 1, c], [r - 1, c], [r, c + 1], [r, c - 1]);
}
return image;
}/** Graph DFS template: count components + flood fill on a grid. */
export function countComponents(n: number, edges: [number, number][]): number {
const adj: number[][] = Array.from({ length: n }, () => []);
for (const [a, b] of edges) { adj[a].push(b); adj[b].push(a); }
const visited = new Array<boolean>(n).fill(false);
let count = 0;
for (let v = 0; v < n; v++) {
if (!visited[v]) { dfs(v); count++; }
}
return count;
function dfs(v: number) {
visited[v] = true;
for (const u of adj[v]) if (!visited[u]) dfs(u);
}
}
export function floodFill(
image: number[][], sr: number, sc: number, newColor: number,
): number[][] {
const orig = image[sr]![sc]!;
if (orig === newColor) return image;
const rows = image.length, cols = image[0]!.length;
const stack: [number, number][] = [[sr, sc]];
while (stack.length) {
const [r, c] = stack.pop()!;
if (r < 0 || c < 0 || r >= rows || c >= cols || image[r]![c] !== orig) continue;
image[r]![c] = newColor;
stack.push([r + 1, c], [r - 1, c], [r, c + 1], [r, c - 1]);
}
return image;
}- 1#133 Clone GraphGuidemedium
- 2#200 Number of IslandsGuidemedium
- 3#417 Pacific Atlantic Water FlowGuidemedium
- 4#547 Number of ProvincesGuidemedium
- 5#695 Max Area of IslandGuidemedium
- 6#733 Flood FillGuideeasy