Topological Sort using DFS solves the Topological Sort problem for directed acyclic graphs (DAGs).
Prerequisites: If you have not read the Depth-First Search page, you should do so before continuing, as topological sorting via DFS builds directly upon the core DFS algorithm and concepts like discovery/finish times and edge classification.
Topological sorting produces a linear ordering of vertices in a directed acyclic graph (DAG) such that for every directed edge \((u, v)\), vertex \(u\) appears before vertex \(v\) in the ordering. This algorithm is classified under Exhaustive Search because it must examine every vertex and edge in the graph to ensure the complete ordering is correct and to detect any cycles that would make topological sorting impossible.
The DFS-based approach leverages the structure of depth-first traversal to naturally produce a topological ordering. The key insight is that when DFS finishes exploring a vertex (records its finish time), all of its descendants in the DFS tree have already been completely processed. This property allows us to construct a valid topological order using either finish times or an explicit stack.
DFS creates a natural topological ordering because of how it processes vertices. When a vertex \(v\) finishes (all its descendants are explored), any vertex \(u\) with an edge \((u, v)\) must either:
This ensures that vertices with no outgoing edges finish first, followed by vertices that depend only on already-finished vertices, creating a valid topological order when vertices are sorted by decreasing finish time.
Consider this example DAG with DFS timestamps (Hopefully you can work through the example in your head and determine that this is how DFS might have progressed):
The diagram shows how DFS timestamps naturally create a valid topological ordering. Notice that for every directed edge \((u, v)\), vertex \(u\) has a larger finish time than vertex \(v\). This happens because:
Therefore, sorting vertices by decreasing finish time automatically produces a valid topological order: G → C → B → A → D → E → F.
There are two main ways to extract the topological ordering from DFS:
Topological sorting only works on directed acyclic graphs (DAGs). The algorithm must detect cycles and fail gracefully if any exist. DFS naturally detects cycles through back edges—edges that lead to vertices currently being processed (gray vertices). If any back edge is found, the graph contains a cycle and cannot be topologically sorted.
Here is the complete algorithm using the stack-based approach:
topologicalSort()
n
be the number of vertices in the graph.color[0..n−1] = WHITE
stack = empty
v
from 0
to n−1
:
color[v] = WHITE
:dfsVisit(v)
returns true
, return "Graph has cycle - no topological sort exists"dfsVisit(v)
→ boolean
color[v] = GRAY
u
of v
:
color[u] = WHITE
:dfsVisit(u)
returns true
, return true
color[u] = GRAY
: return true
// Back edge - cycle detectedcolor[v] = BLACK
v
onto stack
false
// No cycle found▶ Cycle Detection: The algorithm includes explicit checking for back edges (edges to GRAY vertices) and terminates immediately if any cycle is detected, since topological sorting is impossible on cyclic graphs.
▶ Stack-Based Output: Instead of just marking vertices as visited, the algorithm collects vertices in a stack as they finish. Popping the stack gives vertices in reverse finish-time order—exactly the topological ordering needed.
Why This Works: When a vertex finishes in DFS, all vertices reachable from it have already finished. This means the vertex has no unprocessed dependencies, making it safe to place before all previously finished vertices in the final ordering. The stack naturally produces this reverse-finish-time ordering.
The demo below shows both the cycle detection and topological sorting capabilities of the DFS-based algorithm:
Both approaches produce the same result but in different ways:
If a cycle is detected (via a back edge to a gray vertex), both approaches immediately terminate and report that no topological ordering exists.
These implementations show the stack-based approach for topological sorting with cycle detection. The timestamp approach would require sorting vertices by finish time after DFS completes.
Note that in this implementation, dfsVisit
returns a boolean value: true
if a cycle is detected during the traversal of that subgraph, false
otherwise.
This allows us to quit the algorithm as soon as we detect a cycle.
Implementation Notes:
List<Integer>[]
array of lists for graph representationStack<Integer>
to collect vertices in finish orderArrayList<>
for final topological orderingvector<int> adj[]
array of vectors for graph representationstack<int>
to collect vertices in finish ordervector<int>
for final topological orderingadj
for graph representationlist
as stack to collect vertices in finish order[::-1]
to reverse for final orderingList<Integer> topologicalSort(List<Integer>[] adj, int n) {
int[] color = new int[n]; // 0=WHITE, 1=GRAY, 2=BLACK
Stack<Integer> stack = new Stack<>();
for (int v = 0; v < n; v++) {
if (color[v] == 0) { // WHITE
if (dfsVisit(v, adj, color, stack)) {
return null; // Cycle detected
}
}
}
List<Integer> result = new ArrayList<>();
while (!stack.isEmpty()) {
result.add(stack.pop());
}
return result;
}
boolean dfsVisit(int v, List<Integer>[] adj, int[] color,
Stack<Integer> stack) {
color[v] = 1; // GRAY
for (int u : adj[v]) {
if (color[u] == 0) { // WHITE
if (dfsVisit(u, adj, color, stack)) {
return true; // Cycle found in recursion
}
} else if (color[u] == 1) { // GRAY - back edge
return true; // Cycle detected
}
// BLACK vertices (color[u] == 2) are ignored
}
color[v] = 2; // BLACK
stack.push(v);
return false; // No cycle found
}
vector<int> topologicalSort(vector<int> adj[], int n) {
vector<int> color(n, 0); // 0=WHITE, 1=GRAY, 2=BLACK
stack<int> stk;
for (int v = 0; v < n; v++) {
if (color[v] == 0) { // WHITE
if (dfsVisit(v, adj, color, stk)) {
return {}; // Empty vector indicates cycle
}
}
}
vector<int> result;
while (!stk.empty()) {
result.push_back(stk.top());
stk.pop();
}
return result;
}
bool dfsVisit(int v, vector<int> adj[], vector<int>& color,
stack<int>& stk) {
color[v] = 1; // GRAY
for (int u : adj[v]) {
if (color[u] == 0) { // WHITE
if (dfsVisit(u, adj, color, stk)) {
return true; // Cycle found in recursion
}
} else if (color[u] == 1) { // GRAY - back edge
return true; // Cycle detected
}
// BLACK vertices (color[u] == 2) are ignored
}
color[v] = 2; // BLACK
stk.push(v);
return false; // No cycle found
}
def topological_sort(adj, n):
color = [0] * n # 0=WHITE, 1=GRAY, 2=BLACK
stack = []
def dfs_visit(v):
color[v] = 1 # GRAY
for u in adj[v]:
if color[u] == 0: # WHITE
if dfs_visit(u):
return True # Cycle found in recursion
elif color[u] == 1: # GRAY - back edge
return True # Cycle detected
# BLACK vertices (color[u] == 2) are ignored
color[v] = 2 # BLACK
stack.append(v)
return False # No cycle found
for v in range(n):
if color[v] == 0: # WHITE
if dfs_visit(v):
return None # Cycle detected
return stack[::-1] # Reverse to get correct order
Time Complexity: The time complexity is \(O(V + E)\), identical to standard DFS:
Space Complexity: Auxiliary space is \(O(V)\):
This is optimal for topological sorting since we must examine every vertex and edge to ensure correctness.