DFS (Depth-First) Visualization — Step by Step
Explores a graph by going as deep as possible along each branch before backtracking, using a stack.
// quick answer
DFS (Depth-First) is a graph algorithm that runs in O(V+E) time on average with O(V) space. Explores a graph by going as deep as possible along each branch before backtracking, using a stack.
How DFS (Depth-First) Works
Depth-First Search explores as far as possible along each branch before backtracking. This iterative implementation uses an explicit stack. DFS is useful for topological sorting, detecting cycles, finding connected components, and solving maze problems. Time complexity is O(V + E).
DFS (Depth-First) Step by Step
A stack drives everything
DFS commits to one branch and follows it as deep as it can go before backing up — and that behaviour comes entirely from using a LIFO [stack](/workspace/stack). The most recently discovered node is processed first, so the traversal keeps extending its newest path instead of returning to older discoveries. Recursion gives you the same thing implicitly: the call stack *is* the stack. This visualizer uses the explicit iterative form — push the start node, then loop: pop, visit, push unvisited neighbours. Swap the stack for a queue and the same loop becomes BFS.
A concrete trace
Use the graph with edges 0–1, 0–4, 1–2, 1–3, and 3–4, starting at 0. Push 0 → stack [0]. Pop 0, visit, push neighbours 4 then 1 (reversed, so 1 ends on top) → [4, 1]. Pop 1, visit, push 3 then 2 → [4, 3, 2]. Pop 2, visit — dead end, its only neighbour 1 is visited. Pop 3, visit, push its unvisited neighbour 4 → [4, 4]. Pop 4, visit. The leftover 4 is popped and skipped. Visit order: 0, 1, 2, 3, 4 — one deep dive, not expanding rings.
Backtracking is just popping
Watch what happened at node 2: it had no unvisited neighbours, so the dive was stuck. DFS "backtracks" — but no special code exists for that. The next pop simply yields node 3, which was pushed while visiting node 1, so the traversal naturally resumes from the deepest point that still has unexplored options. That is the elegance of the stack formulation: the stack *remembers* every branch point for you. In the recursive version the same thing appears as a function call returning to its caller and the loop over neighbours continuing.
Same graph, opposite order to BFS
Run BFS on the identical graph and it visits 0, 1, 4, 2, 3 — both of 0's neighbours before anything deeper. DFS instead reached node 4 *last*, through the long path via 3, even though 4 sits one edge from the start. This is the crucial caveat: DFS says nothing about shortest paths. The route it finds to a node can be arbitrarily longer than the best one. What DFS gives you instead is deep structural information — discovery order, dead ends, and back edges — which is exactly what cycle detection and topological sorting need.
Duplicates on the stack are fine
In the trace, node 4 appeared on the stack twice: once pushed from 0, once from 3. That is normal for iterative DFS — a node may be reachable from several frontier nodes before either copy is popped. Correctness is preserved by checking the visited set *at pop time*: the first copy popped gets visited, later copies are discarded with a continue. The visualizer shows this as pop events on already-visited nodes that cause no state change. Cost stays O(V + E): each edge pushes at most one stack entry, and each vertex is visited once.
Time & Space Complexity
| Best Case | Average Case | Worst Case | Space |
|---|---|---|---|
| O(V+E) | O(V+E) | O(V+E) | O(V) |
DFS (Depth-First) Pseudocode
function DFS(graph, start):
stack = [start]
visited = {}
while stack not empty:
node = stack.pop()
if node in visited: continue
visited.add(node)
process node
for neighbor in adj[node]:
if neighbor not visited:
stack.push(neighbor)Press play in the visualizer above to watch each line execute with live highlighting.
DFS (Depth-First)Code in JavaScript, Python & Java
JavaScript
function dfs(graph, start) {
// graph: Map or object mapping node -> array of neighbours
const visited = new Set();
const stack = [start];
const order = [];
while (stack.length > 0) {
const node = stack.pop(); // LIFO
if (visited.has(node)) continue; // stale duplicate entry
visited.add(node);
order.push(node);
// Reverse so the first-listed neighbour is explored first
for (const neighbor of [...graph[node]].reverse()) {
if (!visited.has(neighbor)) {
stack.push(neighbor);
}
}
}
return order; // nodes in depth-first visit order
}Python
def dfs(graph, start):
"""Iterative DFS. graph: dict mapping node -> list of neighbours."""
visited = set()
stack = [start]
order = []
while stack:
node = stack.pop() # LIFO
if node in visited:
continue # stale duplicate entry
visited.add(node)
order.append(node)
# reversed() so the first-listed neighbour is explored first
for neighbor in reversed(graph[node]):
if neighbor not in visited:
stack.append(neighbor)
return order # nodes in depth-first visit orderJava
import java.util.*;
public class DFS {
// graph: adjacency list, graph.get(u) = neighbours of u
public static List<Integer> dfs(List<List<Integer>> graph, int start) {
Set<Integer> visited = new HashSet<>();
Deque<Integer> stack = new ArrayDeque<>();
List<Integer> order = new ArrayList<>();
stack.push(start);
while (!stack.isEmpty()) {
int node = stack.pop(); // LIFO
if (visited.contains(node)) continue; // stale duplicate
visited.add(node);
order.add(node);
// Push in reverse so the first neighbour is explored first
List<Integer> neighbors = graph.get(node);
for (int i = neighbors.size() - 1; i >= 0; i--) {
int neighbor = neighbors.get(i);
if (!visited.contains(neighbor)) stack.push(neighbor);
}
}
return order;
}
}When to Use DFS (Depth-First)
- Use it for cycle detection and topological sorting of dependency graphs (build systems, task schedulers, package managers) — both fall out of DFS discovery/finish order naturally.
- Use it for exhaustive exploration and backtracking problems: maze solving, puzzle solvers, and generating permutations are all DFS over a state space, abandoning a branch when it fails.
- Use it to find connected components or check reachability when any path will do — it is as fast as BFS at O(V + E) and often simpler to write recursively.
- Prefer it on deep-but-narrow search spaces: the stack only holds the current path plus siblings, which can be far smaller than the full frontier a BFS queue must carry on wide graphs.
- Do not use it when you need the shortest path — DFS can reach a node one edge away via a ten-edge detour. Use BFS for unweighted graphs or Dijkstra for weighted ones.
- Watch recursion depth in the recursive form: a path of 100,000 nodes will overflow the call stack in most languages. Switch to the explicit-stack iterative version for very deep graphs.
DFS (Depth-First) — Frequently Asked Questions
Does DFS find the shortest path?
No. DFS commits to a branch and follows it to the end, so it can reach a node that is one edge from the start via a much longer detour, and the first path it finds carries no optimality guarantee. If you need the fewest edges, use breadth-first search; if edges have weights, use Dijkstra. DFS answers reachability and structure questions, not distance questions.
Is iterative DFS the same as recursive DFS?
They visit the same nodes with the same asymptotic cost, but the raw iterative version explores each node's neighbours in reverse order compared to the recursive version, because a stack reverses what you push. Pushing neighbours in reversed order — as this implementation does — makes the two orders match. The iterative form also avoids call-stack overflow on very deep graphs, which is why it is often preferred in production.
When should I use DFS instead of BFS?
Reach for DFS when the question is about structure or exhaustive exploration rather than distance: detecting cycles, topological sorting, finding connected components, or backtracking through a puzzle or maze. It also uses less memory than BFS on wide graphs, since it stores one path at a time rather than a whole frontier. If you need shortest paths or level-by-level processing, BFS is the right tool.
What is the time and space complexity of DFS?
Time is O(V + E): each vertex is visited once and each edge is examined a constant number of times. Space is O(V) for the visited set plus the stack, which in the worst case — a long chain — holds a path through every vertex. On graphs where several frontier nodes point at the same undiscovered node, the stack may briefly hold duplicate entries, but the total never exceeds O(V + E) pushes.
Why can the same node appear twice on the DFS stack?
Iterative DFS pushes a node whenever any visited node discovers it, and two different nodes can discover the same neighbour before either copy gets popped. This is harmless as long as the visited check happens at pop time: the first copy popped is visited and marked, and every later copy is skipped. Marking nodes only at push time is an alternative, but then you must be careful not to push a node twice.
Compare DFS (Depth-First)
- DFS (Depth-First) vs BFS (Breadth-First) — when to choose which, head to head
Related Algorithms
- BFS (Breadth-First) Visualization — Explores a graph level by level using a queue. Finds shortest path in unweighted graphs.
- Dijkstra's Algorithm Visualization — Finds the shortest path from a source to all other nodes in a weighted graph with non-negative edges.
References & Further Reading
- Wikipedia — Depth-first search
- Cormen, Leiserson, Rivest & Stein — Introduction to Algorithms (4th ed., MIT Press)