Depth-First Search

Graphs
avg O(V + E)

Depth-first search explores a graph the way you explore a maze: pick a corridor, keep going deeper, and only walk back when you hit a dead end. It is the recursion-shaped traversal - the call stack quietly remembers the whole way back. That simple loop of "go deep, back up, try the next branch" powers cycle detection, topological sort, maze generation, and every "is there any path?" question. BFS asks "what is closest?"; DFS asks "where can I reach?".

Watch it dive and back up

Press play - or drag the timeline and step through it yourself.
Depth 1Steps 0Step 1 / 21
ABCDEFGH

Depth-first search from A: always go deeper if you can, back up only when stuck. The stack remembers the way back. Neighbours are tried in alphabetical order.

top of the stack on the stack fully explored tree edge back edge / skip

Complexity

Best
O(V + E)
Input already in order
Average
O(V + E)
Normal mixed input
Worst
O(V + E)
Worst possible input
Space
O(V)
Extra memory used

A maze and a ball of string

You enter a maze holding a ball of string, and you tie the end at the entrance. At every junction you take the first corridor you have not tried yet, unrolling string as you walk, and you chalk-mark every junction you visit. Dead end? Follow the string back to the last junction and take its next untried corridor. If a corridor leads to a chalked junction that your string already runs through, you have walked in a circle - that is a cycle. The string is the call stack: it always knows the way back, one junction at a time. When you are back at the entrance with no corridors left, you have seen the whole maze.

How it works, step by step

  1. Start at a node. Mark it visited and give it discovery number 1.

  2. Look at its neighbours in order. Go to the first unvisited one right away - do not finish the list first.

  3. Repeat from the new node: always go deeper while any unvisited neighbour exists. The path you are standing on is the stack.

  4. An edge that leads to a node already on the stack is a back edge - it closes a cycle. Note it, but do not walk it.

  5. No unvisited neighbours left? Backtrack: pop the stack, step back one node, and try its next neighbour.

  6. When you pop the start node, you are done. Every node and edge is handled once - O(V + E).

The code, in JavaScript

The natural form: the function call stack IS the algorithm's stack. Going deeper is a call; backtracking is a return. This is the graph from the animation above.

javascript
function dfs(graph, start) {
  const visited = new Set();
  const order = [];

  function explore(node) {
    visited.add(node);          // mark BEFORE recursing, or a cycle loops forever
    order.push(node);
    for (const next of graph[node] ?? []) {
      if (!visited.has(next)) {
        explore(next);          // go deeper NOW - the rest of the list waits
      }
    }
  }                             // returning from explore() = backtracking

  explore(start);
  return order;
}

const graph = {
  A: ["B", "C"], B: ["A", "D", "E"], C: ["A", "F", "G"],
  D: ["B", "H"], E: ["B", "H"], F: ["C"], G: ["C"], H: ["D", "E"],
};

dfs(graph, "A");   // → ["A", "B", "D", "H", "E", "C", "F", "G"]

Dry run: DFS from node A

The same steps the visualizer plays, written as a table. Generated by running the real algorithm - not written by hand.

StepDepthWhat happened
11Visit A and mark it #1. Stack: [A].
22Go deeper to B (#2). Stack: [A, B].
33Go deeper to D (#3). Stack: [A, B, D].
44Go deeper to H (#4). Stack: [A, B, D, H].
55Go deeper to E (#5). Stack: [A, B, D, H, E].
65E sees B - but B is already on my path (the stack). That is a cycle: B-D-H-E-B. Do not walk it.
74E has no unvisited neighbours - back up to H. Stack: [A, B, D, H].
83H has no unvisited neighbours - back up to D. Stack: [A, B, D].
92D has no unvisited neighbours - back up to B. Stack: [A, B].
102B still has an edge to E - but E is already fully explored. Nothing new that way.
111B has no unvisited neighbours - back up to A. Stack: [A].
122A tries its next branch - go deeper to C (#6). Stack: [A, C].
133Go deeper to F (#7). Stack: [A, C, F].
142F is a dead end - back up to C. Stack: [A, C].
153C tries its next branch - go deeper to G (#8). Stack: [A, C, G].
162G is a dead end - back up to C. Stack: [A, C].
171C has no unvisited neighbours - back up to A. Stack: [A].
181A has no branches left - pop it. The stack is empty.

Good choice when…

  • You only need to know if a path exists, or want everything reachable from a node - DFS gets there with a lean stack instead of BFS's wide frontier.
  • You need cycle detection, topological sort, or strongly connected components - all three are DFS plus a little bookkeeping.
  • Backtracking search: mazes, sudoku, n-queens. "Try a move, recurse, undo" is DFS over the graph of game states.
  • Maze generation: a randomized DFS that carves corridors produces the classic long, winding mazes.

Bad choice when…

  • You need the shortest path in an unweighted graph. That is BFS's superpower - DFS returns whatever path it stumbles into first.
  • The graph can be very deep and you wrote the recursive version - a 100k-node chain means 100k stack frames. Use the iterative form.
  • You want nodes level by level (nearest first, k-hop neighbours). DFS order jumps around; BFS order is sorted by distance.

Common mistakes

  • Recursive DFS overflows the call stack on deep graphs - in most JavaScript engines somewhere around 10k frames. A path-shaped graph (a long chain, one maze corridor) is enough to crash it. Production DFS on unknown input should be iterative.
  • The iterative version visits neighbours in reverse order compared to the recursive one, because a stack flips what you push. Same nodes visited, different order - push the neighbour list reversed if the order must match.
  • For cycle detection in a directed graph, one `visited` set is wrong: it mixes up "on my current path" with "finished long ago", and only an edge back to the current path proves a cycle. When you mark matters too - gray goes on when you enter a node, black only when you leave it.
  • DFS does not find shortest paths, but people reach for it out of habit. The first path it finds depends on neighbour order and can be absurdly long. "Fewest steps" questions are BFS questions, every time.

Depth-First Search vs. its closest relatives

AlgorithmBestAverageWorstSpaceStable
Depth-First Searchthis pageO(V + E)O(V + E)O(V + E)O(V)-
Breadth-First SearchO(V + E)O(V + E)O(V + E)O(V)-
Topological SortO(V + E)O(V + E)O(V + E)O(V)-
Tree TraversalsO(n)O(n)O(n)O(h)-