Topological Sort — Ordering Dependencies¶
Topological sort answers one question: given a set of tasks with dependencies, in what order should the tasks be performed? It produces a linear ordering of vertices in a directed graph such that for every directed edge u→v, vertex u appears before vertex v in the ordering.
This only makes sense on a directed acyclic graph (DAG). If the graph has a cycle, no such ordering exists — you’d have a circular dependency where task A requires B, B requires C, and C requires A. Topological sort is therefore simultaneously an ordering algorithm and a cycle detector.
Existence Condition¶
A topological ordering exists if and only if the graph is a DAG. If the graph contains any cycle, topological sort will fail to produce an ordering that covers all vertices — and this failure is how you detect the cycle.
Algorithm 1: Kahn’s Algorithm (BFS-Based)¶
Kahn’s algorithm is the more intuitive of the two. The idea: nodes with no incoming edges (in-degree 0) have no prerequisites — they can go first. Process them, remove their edges, and repeat. If at any point there are no zero-in-degree nodes left but unprocessed nodes remain, there’s a cycle.
Step-by-step:
Compute in-degree for every vertex
Enqueue all vertices with in-degree 0
Dequeue a vertex, add to result, decrement in-degree of all its neighbors
Enqueue any neighbor whose in-degree just dropped to 0
Repeat until queue is empty
If result contains all V vertices: valid topological order. If fewer: graph has a cycle.
List<Integer> kahnTopologicalSort(int V, List<List<Integer>> adj) {
int[] inDegree = new int[V];
// Step 1: compute in-degree
for (int u = 0; u < V; u++)
for (int v : adj.get(u))
inDegree[v]++;
// Step 2: enqueue all zero-in-degree nodes
Queue<Integer> queue = new LinkedList<>();
for (int i = 0; i < V; i++)
if (inDegree[i] == 0)
queue.offer(i);
List<Integer> result = new ArrayList<>();
// Steps 3-5
while (!queue.isEmpty()) {
int node = queue.poll();
result.add(node);
for (int neighbor : adj.get(node)) {
inDegree[neighbor]--;
if (inDegree[neighbor] == 0)
queue.offer(neighbor);
}
}
// Step 6: cycle detection
if (result.size() != V) return new ArrayList<>(); // cycle exists
return result;
}
Time: O(V + E). Every vertex is enqueued/dequeued once, every edge is processed once. Space: O(V) for in-degree array + queue.
Algorithm 2: DFS-Based Topological Sort¶
The observation: in DFS, a node finishes (gets its finish time) only after all nodes reachable from it have finished. So the finish order is the reverse topological order — nodes that must come last finish first.
void dfsTopSort(int node, boolean[] visited, Deque<Integer> stack, List<List<Integer>> adj) {
visited[node] = true;
for (int neighbor : adj.get(node)) {
if (!visited[neighbor]) {
dfsTopSort(neighbor, visited, stack, adj);
}
}
stack.push(node); // push AFTER all descendants are processed
}
List<Integer> dfsTopologicalSort(int V, List<List<Integer>> adj) {
boolean[] visited = new boolean[V];
Deque<Integer> stack = new ArrayDeque<>();
for (int i = 0; i < V; i++)
if (!visited[i])
dfsTopSort(i, visited, stack, adj);
List<Integer> result = new ArrayList<>();
while (!stack.isEmpty()) result.add(stack.pop());
return result;
}
The result is the reverse of the stack: nodes popped first are the ones that must appear first in the topological order.
Note: the DFS-based version doesn’t naturally detect cycles without the three-color scheme. Use Kahn’s when you also need cycle detection (which is most interview problems).
Cycle Detection via Kahn’s¶
The cycle detection property of Kahn’s is worth emphasizing: if there’s a cycle, no node in the cycle ever reaches in-degree 0 (because each node in the cycle depends on another node in the cycle). So the queue empties before all nodes are processed. result.size() < V is the cycle indicator.
This is used directly in: Course Schedule (LC 207), Course Schedule II (LC 210), Build Dependency Order.
What most people get wrong: building the graph with edges in the wrong direction. If the problem says “course b is a prerequisite of course a” (meaning b must come before a), the directed edge is b→a, not a→b. Getting the edge direction backwards gives you a reversed topological order, which fails silently on small test cases and breaks on larger ones.
The phrasing to watch for:
“a depends on b” → edge b→a (b must come before a)
“a must be completed before b” → edge a→b
“there is a directed edge from u to v” → edge u→v (read the problem literally)
Before coding, write out 2-3 nodes and their constraint explicitly: “node X must appear before node Y, so edge is X→Y.” Verify your graph construction on this example.
Applications¶
Build systems (Make, Gradle, Maven): source files have compilation dependencies. Topological sort determines build order.
Course prerequisites: a classic — if course A requires course B, B must be taken first. Topological sort gives a valid semester sequence.
Task scheduling: any project with task dependencies can be scheduled in topological order.
Dependency resolution (npm, pip): installing packages in an order that satisfies all dependencies.
Evaluating formulas in a spreadsheet: cell A depends on B, B depends on C. Evaluate in topological order.
Compiler type resolution: if type A uses type B, B must be defined first.
Practice Problems¶
# |
Problem |
Difficulty |
Core |
|---|---|---|---|
1 |
LC 207 — Course Schedule |
Medium |
Cycle detection via Kahn’s |
2 |
LC 210 — Course Schedule II |
Medium |
Return topological order |
3 |
LC 269 — Alien Dictionary |
Hard |
Infer directed graph from word order, topological sort |
4 |
LC 1136 — Parallel Courses |
Medium |
Topological sort + level timing |
Problem 3 (Alien Dictionary) is the hard problem that tests whether you can construct the graph from non-obvious constraints, not just run the algorithm on a given graph. The trick: compare adjacent words in the sorted list character-by-character to infer ordering. The first character where two words differ gives you a directed edge. Build the graph, run topological sort.
For LC 1136 (Parallel Courses): the answer is the number of “levels” in the topological order — courses that can be taken simultaneously form one level. This is Kahn’s algorithm where you track the level number: every batch of nodes dequeued together forms one semester.