DFS Visualization —
Dive deep, then backtrack.
Depth-First Search commits to one path and follows it as far as it can go before backtracking to try another branch — powered by recursion or an explicit stack. It's the natural choice for "does a path exist," cycle detection, and exploring every connected node. Watch the recursion dive to a dead end and unwind on LearnBug, one backtrack at a time.
What is DFS?
From a node, visit an unvisited neighbor, then immediately recurse into that neighbor's unvisited neighbor, and so on — going as deep as possible before backtracking. When a node has no unvisited neighbors left, the recursion returns to whichever call is still waiting to try its next neighbor.
Why visualization helps
The backtrack step is the part that's invisible in code — a function just returns, and control silently resumes at the caller. LearnBug highlights the current node and shows the call stack shrinking on every backtrack, so "dead end, go back and try the next branch" is something you watch instead of infer.
Recursive call stack vs explicit stack
Recursive
Python's own call stack tracks where to backtrack to. Concise, but limited by Python's recursion depth on very deep graphs.
Iterative
An explicit stack replaces recursion — same LIFO behavior, no recursion-depth limit, but visit order can differ slightly depending on push order.
Cycle detection
DFS with a "currently in recursion stack" marker is the standard way to detect a cycle in a directed graph — see the next lesson.
DFS from A on A→[B,C], B→[D], C→[D]
Visit A, recurse into its first unvisited neighbor B
A has two neighbors, B and C. DFS commits to the first one — B — and dives into it immediately, without looking at C yet.
Visit B, recurse into its only neighbor D — still diving
B has one neighbor, D, which is unvisited. DFS keeps diving deeper instead of backtracking to try C yet.
Visit D — dead end, no unvisited neighbors — backtrack all the way to A
D has no neighbors, so dfs(D) returns immediately. dfs(B) has no more neighbors either, so it also returns. Control lands back in dfs(A), ready to try C.
Back in dfs(A), recurse into C — D is already visited, so it's skipped
C's only neighbor is D, but D is already in the visited set, so dfs(C) does nothing further and returns. The full traversal is done.
Return: [A, B, D, C] ✓ dove all the way down one branch before trying the other
Recursive and iterative DFS
graph = {
"A": ["B", "C"],
"B": ["D"],
"C": ["D"],
"D": [],
}
visited = []
def dfs(node):
if node in visited:
return
visited.append(node) # visit
for neighbor in graph[node]:
dfs(neighbor) # dive before trying the next neighbordef dfs_iterative(start):
stack, visited, order = [start], [], []
while stack:
node = stack.pop()
if node in visited:
continue
visited.append(node)
order.append(node)
# push in reverse so the FIRST neighbor is popped FIRST
for neighbor in reversed(graph[node]):
if neighbor not in visited:
stack.append(neighbor)
return orderComplexity Notes
Frequently asked questions
When should I use DFS instead of BFS?
Use DFS when you need to know if a path exists, need to explore every node of a connected component, or need to detect cycles — you don't care about finding the shortest path. Use BFS specifically when "fewest edges" or "minimum steps" matters.
Why does the iterative version push neighbors in reverse order?
A stack is LIFO, so whatever is pushed last gets popped first. To visit neighbors in the same left-to-right order the recursive version naturally follows, you push them in reverse — so the first neighbor in the list ends up on top of the stack and gets popped first.
Can DFS get stuck in an infinite loop?
Only if you forget the visited check. Without it, a cycle in the graph (A → B → A) would cause DFS to recurse forever. The visited list/set is not optional — it's what guarantees termination on graphs with cycles.
Does the order DFS visits nodes matter for correctness?
For "does a path exist" or "find all connected nodes," no — any valid DFS order reaches the same set of nodes. For problems like topological sort or cycle detection, the specific structure of the recursion (not just the order) is what the algorithm relies on.
Watch the recursion dive deep, then backtrack
Paste your DFS into LearnBug and see the call stack grow and shrink at every step.