Data Structures & Algorithms

Depth-First Search

Follow one path as far as it goes, then backtrack. The basis for cycle detection, topological sort, and exhaustive search.

Level: intermediate

What is it?

Depth-first search (DFS) explores a graph by going as deep as possible along each branch before backtracking. It is naturally recursive (the call stack remembers the way back) or can use an explicit stack. A visited set prevents infinite loops on cycles. Recording the order in which vertices finish yields a topological ordering of a DAG.

Why does it matter?

DFS answers structural questions — is there a cycle, which vertices are connected, what is a valid build order, can this maze be solved — in linear time. It is also the engine of backtracking search over puzzles and combinations.

Mental model

Exploring a cave with a ball of string. Go down a tunnel as far as it goes, and at a dead end wind the string back to the last fork and try the next tunnel. The string is the stack of choices you can still return to.

How it works

  • Recursive form: mark visited, recurse into each unvisited neighbor.
  • Iterative form: an explicit stack avoids recursion limits on deep graphs.
  • Time O(V + E); space O(V) for the visited set plus O(depth) for the stack.
  • Detect a cycle in a directed graph by finding an edge to a vertex still on the current path.
  • Topological sort: order vertices by decreasing finish time (or use Kahn's algorithm).
  • Backtracking is DFS over the tree of choices, with a step that undoes each choice.

Steps

  • Mark the vertex visited
  • Pick an unvisited neighbor
  • Recurse deeper
  • At a dead end, return
  • Try the next neighbor

Code example: Recursive depth-first traversal

def dfs(graph, node, seen=None):
    seen = set() if seen is None else seen
    seen.add(node)
    for nxt in graph[node]:
        if nxt not in seen:
            dfs(graph, nxt, seen)
    return seen

g = {"A": ["B", "C"], "B": ["D"], "C": [], "D": []}
print(sorted(dfs(g, "A")))   # ['A', 'B', 'C', 'D']

Complexity and trade-offs

Time: O(V + E)

Space: O(V) for the visited set; O(depth) for the stack

Trade-offs

  • Low memory on wide graphs and a natural fit for recursion
  • Does not find shortest paths
  • Deep recursion can overflow the stack

Real-world usage

  • Cycle detection and topological sorting of dependencies
  • Connected components and maze solving
  • Backtracking: Sudoku, N-Queens, and permutations

Common mistakes

  • Forgetting a visited set and looping forever on a cycle.
  • Using DFS for shortest paths.
  • Recursing on a huge path graph and hitting the recursion limit.

Interview questions

How do you detect a cycle in a directed graph with DFS?

Track vertices currently on the recursion path. If you reach a neighbor that is on that path, there is a back edge, and therefore a cycle. Vertices finished earlier do not count.

How does DFS give a topological order?

Run DFS and append each vertex to a list after all its descendants are finished. Reversing that list orders vertices so every edge points forward.

What is backtracking?

A DFS over the space of partial solutions: extend the current choice, recurse, and undo the choice when it leads to a dead end. Pruning invalid branches early makes it practical.

Prerequisites

Related concepts

Continue learning

Browse the full computer science library