DFS of Graph
Return the depth-first traversal order of a graph given as an adjacency list.
Open on GeeksforGeeks ↗Intuition
DFS commits to one path and follows it as far as it goes before backing up. The only bookkeeping needed is a visited set — without it any cycle sends the traversal round forever. Mark a vertex the moment you arrive, not when you leave, so a neighbour can never queue it a second time.
The base traversal every graph algorithm is built on. Two details make it correct rather than merely plausible: mark a vertex on arrival (not on departure), and launch from every unvisited vertex so disconnected components aren't missed. Cycle detection, topological sort and connected components are all this loop with something extra bolted on.
Approach
Recurse, marking on arrival
dfs(v): mark v visited, record it, then recurse into every unvisited neighbour. Marking before recursing is what makes cycles safe — a neighbour that loops back finds v already marked and stops.
Handle disconnected graphs
A single dfs(0) only reaches vertices connected to 0. Loop over all vertices and start a fresh DFS from each unvisited one, so every component is covered.
Recursion or explicit stack
The call stack is doing the work of a stack; swapping in an explicit list gives the same order (push neighbours in reverse to match) and avoids stack-overflow on deep graphs.
Solution & live demo
Common pitfalls
Marking visited after the recursive call
def dfs(v):
for u in adj[v]:
if not seen[u]: dfs(u)
seen[v] = Truedef dfs(v):
seen[v] = True
order.append(v)
for u in adj[v]:
if not seen[u]: dfs(u)On any cycle two vertices recurse into each other before either is marked, and the recursion never terminates. The mark must be set the moment you arrive, so it's visible to everything reachable from here.
Starting only from vertex 0
dfs(0) return order
for s in range(n):
if not seen[s]: dfs(s)A graph need not be connected, so vertices unreachable from 0 would never appear in the output. Every unvisited vertex needs its own launch.
Checking the visited flag only at the top of dfs
def dfs(v):
if seen[v]: return
seen[v] = True
for u in adj[v]: dfs(u)for u in adj[v]:
if not seen[u]: dfs(u)Both terminate, but the first pushes a stack frame for every edge — including all the ones that immediately return. Filtering before the call keeps the recursion depth proportional to the path, not the edge count.
Edge cases
Outer loop restarts DFS at every unvisited vertex.
The vertex is already marked when the loop edge is examined — skipped.
Visited and recorded with no recursion.