Depth-First Search
Explore as deep as possible before backtracking. One visited set prevents infinite loops. Works on any graph, grid, or tree.
Explore a graph without a visited set — infinite loop. With one — O(V+E)
The insight: a visited set transforms an infinite problem into a finite one. Each node enters the visited set exactly once. The DFS explores the call stack (recursive) or an explicit stack (iterative) — going as deep as possible before backtracking to try other branches.
Two DFS Implementations
Two implementations, same algorithm. Know both cold.
The call stack IS the DFS stack. Mark visited, process node, recurse into unvisited neighbors. Elegant and readable. Limited by Python's recursion depth (~1000 by default).
Explicit stack replaces call stack. Identical traversal order, no recursion limit. Push neighbors in reverse order to match recursive left-to-right ordering.
The 3-Step DFS Framework
The same three steps work for every DFS problem — graph or grid.
From an edge list, build an adjacency list. For grids, the graph is implicit — neighbors are the 4 (or 8) adjacent cells. This step is often where interview bugs live.
graph = defaultdict(list)
for u, v in edges:
graph[u].append(v)
graph[v].append(u) # undirectedCreate a visited set (or boolean array). For disconnected graphs, call DFS from every unvisited node in a for-loop — not just from node 0.
visited = set()
for node in range(n):
if node not in visited:
dfs(node, visited)Add the current node to visited BEFORE calling DFS on neighbors. Marking after is a race condition in graphs — two paths can both try to visit the same node simultaneously.
def dfs(node):
visited.add(node) # mark BEFORE recursing
for nb in graph[node]:
if nb not in visited:
dfs(nb)Pattern Recognition
DFS vs BFS — the most important graph decision.