Introduction
Depth-first search (DFS) explores a graph by going as deep as possible along each path before backtracking. It assigns every vertex a discovery time and a finish time , and it classifies every edge as a tree, back, forward or cross edge. Those timestamps and edge types are the foundation of topological sort and strongly connected components.
Where BFS expands by layers with a queue, DFS expands by a stack (explicit or the call stack). Both run in time on adjacency lists; they answer different questions.
Prerequisites
- Graph Representations: adjacency lists.
- Stacks: LIFO structure matching the DFS recursion stack.
- Breadth-First Search: contrast of queue versus stack traversal.
Core concepts
Timestamps
A global clock time starts at 0. When DFS first discovers it sets and paints grey; when it finishes scanning it sets and paints black. Every vertex gets a unique pair with , and the intervals nest or are disjoint (parenthesis theorem).
Edge classification (directed graphs)
When DFS examines edge :
| Type | Condition when examined | Timestamps after DFS | Meaning |
|---|---|---|---|
| Tree | white | discovered from ; in the DFS forest | |
| Back | grey | is an ancestor of (cycle witness) | |
| Forward | black and | is a descendant already finished | |
| Cross | black and | is in another subtree or earlier tree |
The timestamp column (CLRS Exercise 22.3-5) classifies an edge after the search has finished. Tree and forward edges have the same timestamp pattern; they differ only in whether was discovered through this edge. A cross edge always points to a vertex that was discovered and finished earlier, so it goes "right to left" in a drawing whose trees are ordered by discovery.
In an undirected graph every edge is tree or back (forward and cross do not arise under the standard undirected classification: a non-tree edge always leads to a grey ancestor).
Parenthesis theorem
For any two vertices , exactly one of the following holds:
- the intervals and are disjoint, and neither vertex is a descendant of the other;
- lies inside , and is a descendant of ;
- lies inside , and is a descendant of .
Crossing (partly overlapping) intervals such as never occur.
Subtree size from timestamps. Each vertex receives exactly two timestamps, and all of 's descendants receive theirs between and . Hence (number of vertices in 's DFS subtree, included), whatever the non-tree edges are.
Algorithms and pseudocode
Pseudocode uses adjacency lists, <- for assignment and a global time.
DFS(G)
1 for each vertex u in G.V
2 u.color <- WHITE
3 u.pi <- NIL
4 time <- 0
5 for each vertex u in G.V
6 if u.color = WHITE then
7 DFS-VISIT(G, u)
DFS-VISIT(G, u)
1 time <- time + 1
2 u.d <- time
3 u.color <- GREY
4 for each v in Adj[u]
5 if v.color = WHITE then
6 v.pi <- u
7 DFS-VISIT(G, v)
8 u.color <- BLACK
9 time <- time + 1
10 u.f <- timeInput. Graph , directed or undirected.
Output. Discovery and finish times, predecessors forming a DFS forest, and (by the colour of the head when an edge is examined) an edge classification.
Time / space. on adjacency lists: each vertex and each edge is processed a constant number of times. Stack depth in the worst case (a path). Extra space for colours and timestamps.
Edge cases. Disconnected graphs produce a forest (outer loop starts a new tree at each white vertex). Self-loops in a directed graph are back edges. The forest (and edge types that depend on discovery order) depends on the order of vertices in the outer loop and inside adjacency lists; the parenthesis structure always holds.
How the recursion stack mirrors the forest
At any moment the grey vertices on the call stack form a tree path from the root of the current DFS tree to the vertex now being explored. Discovering a white neighbour extends that path; finishing a vertex pops it. An explicit-stack implementation pushes a frame with the next adjacency-list index and obtains the same forest, which is useful when the language limits recursion depth.
The discovery time of a vertex is the step at which it first becomes grey; the finish time is the step at which it becomes black. Between those two times every descendant is discovered and finished, which is exactly the parenthesis theorem.
Mathematical explanation
White-path theorem
Vertex is a descendant of in the DFS forest if and only if, at the time , there is a path from to consisting entirely of white vertices. This characterises the tree edges DFS will grow.
A path is not enough. A path from to together with does not make a descendant of (CLRS Exercise 22.3-8). Edges , , , with DFS from and : DFS discovers (1), then (2), sees as a back edge, finishes (3), then discovers (4). The path exists and , but is not a descendant of : when was discovered, the path went through the grey vertex .
Cycle detection
A directed graph has a cycle if and only if DFS finds a back edge. An undirected graph has a cycle if and only if DFS finds a back edge (to a grey ancestor other than the immediate parent, depending on how undirected adjacency is represented).
Running-time recurrence view
DFS is not analysed by the master theorem; the bound follows from an aggregate count: the body of DFS-VISIT runs once per vertex, and the adjacency-list scan touches each edge once (twice in undirected representations that store both directions).
Worked examples
Directed graph on four vertices
Vertices . Edges: , , , , . Outer loop order ; adjacency lists in alphabetical order.
DFS starting at :
| Event | time | note |
|---|---|---|
| discover | 1 | |
| discover | 2 | tree |
| discover | 3 | tree |
| discover | 4 | tree |
| examine | grey: back edge | |
| finish | 5 | |
| finish | 6 | |
| finish | 7 | |
| examine | black, : forward | |
| finish | 8 |
Timestamps: . The edge is a back edge, so the graph has a cycle .

Subtree check: and ( and ).
Adding a cross edge. Add a fifth vertex with the single edge , placed last in the outer loop. After 's tree finishes at time 8, the outer loop starts a new tree at : . Edge finds black with , so it is a cross edge, and . If instead came first in the outer loop, would be a tree edge and all five vertices would form one tree. Edge types depend on the vertex order.
Which orders are DFS orders?
Use the undirected graph of the BFS note: edges , , , , , , . Over every ordering of the adjacency lists, DFS from can discover vertices in exactly five orders: ; ; ; ; . The order is impossible: after is discovered, still has the white neighbour , so DFS must go to before backtracking to . IMPERATIVE RULE. The next discovered vertex must be a white neighbour of the deepest grey vertex that still has a white neighbour.
Undirected cycle
Triangle . DFS from with lists for : tree edges , ; the edge is a back edge to the grey ancestor . Finish order .
Discovery order versus finish order
Discovery order is a preorder walk of the DFS forest: a vertex is listed before its descendants. Finish order is a postorder walk: a vertex is listed after its descendants. Topological sort of a DAG is exactly the reverse of the finish order, which is why a single DFS suffices when the graph has no back edges.
Important properties and observations
- Finish times are the key output for later algorithms: topological sort emits vertices in decreasing finish time; Kosaraju's SCC algorithm runs DFS twice and uses finish order on the transpose.
- Tree edges form a forest, not necessarily a single tree.
- Forward and cross edges appear only in directed graphs.
- The number of tree edges is , where is the number of DFS trees. In an undirected graph, is the number of connected components (CLRS Exercise 22.3-12), and the other edges are all back edges.
- A vertex with both incoming and outgoing edges can still form a DFS tree on its own: all of its out-neighbours may already be black when the outer loop reaches it (CLRS Exercise 22.3-11).
- The discovery order is a preorder of the DFS forest; the finish order is a postorder.
Common misconceptions.
- "DFS always produces a single tree." Only if the graph is connected (undirected) or every vertex is reachable from the first source (directed).
- "Back edges exist in every DFS of a DAG." A DAG has no back edges; that is equivalent to acyclicity.
- "A path from to with makes a descendant of ." False; the white-path condition at time is what matters (example above).
- "Replacing BFS's queue by a stack gives the same order as recursive DFS." Pushing every neighbour when a vertex is popped produces a traversal, but its order and timestamps can differ from recursive DFS. A faithful iterative DFS keeps, for each grey vertex, the position reached in its adjacency list.
GATE examination perspective
- CLASSIFY THE EDGE. Given a DFS forest and timestamps, name each remaining edge.
- PARENTHESIS CHECK. Intervals nest or are disjoint; a crossing pair is impossible.
- CYCLE WITNESS. A back edge is necessary and sufficient for a directed cycle.
- ORDER DEPENDENCE. State the adjacency-list order; different orders change the forest but not the bound.
- FINISH ORDER. Topological sort and SCC questions start from a correct finish-time list.
- VALID DFS ORDER. Check each step against the deepest grey vertex that still has a white neighbour.
- TIMESTAMP ARITHMETIC. is twice the subtree size; times run from 1 to .
- COUNT EDGES. Tree edges ; in an undirected graph every other edge is a back edge.
Summary
Sources
- T. H. Cormen, C. E. Leiserson, R. L. Rivest, C. Stein, Introduction to Algorithms, 3rd ed., MIT Press, 2009, Chapter 22, Section 22.3 (depth-first search); Exercises 22.3-5 (classification by timestamps), 22.3-8 (path with d[u] < d[v] counterexample), 22.3-11 (single-vertex tree) and 22.3-12 (components).