Order nodes of a DAG (Directed Acyclic Graph) so every edge goes from earlier to later. Β β± Time
O(V+E)Β πΎ SpaceO(V)
Run DFS on every unvisited node. When all of a node's descendants have been processed (DFS returns), push the node onto a stack. Reversing the stack gives the topological order β because a node is only pushed after everything it depends on.
Algorithm:
def dfs(node):
visited.add(node)
for neighbour in graph[node]:
if neighbour not in visited:
dfs(neighbour)
stack.append(node) β post-order push
for node in graph:
if node not in visited:
dfs(node)
return reversed(stack) β topological order
Graph: C β A β D β G
C β B β D β H
E β F β H
DFS from C:
C β A β D β G (push G) β H (push H) β push D β push A
β B (visited A, go to D: visited)
β push B β push C
E β F β H (visited) β push F β push E
Stack (bottomβtop): [G, H, D, A, B, C, F, E]
Reversed: C, E, B, F, A, D, H, G β valid topo order β
Topological sort was formalised by Arthur Kahn (1962, BFS-based "Kahn's algorithm") and the DFS-based approach by Robert Tarjan (1976). It underpins every build system, package manager, and compiler dependency analysis.
- Only works on DAGs β check for cycles first (or detect cycle during DFS)
- Two approaches: DFS post-order + reverse (shown here) vs Kahn's BFS (in-degree based)
- Kahn's algorithm is often easier to explain: repeatedly remove nodes with in-degree 0
- Topological sort is not unique β multiple valid orderings can exist
- If after topo sort some nodes are unprocessed β cycle exists (Kahn's makes this explicit)
- Build systems (Makefile, Gradle, Bazel β compile in correct order)
- Package managers (npm, pip β install dependencies before packages)
- Course prerequisite scheduling
- Compiler symbol resolution
- Database query plan optimisation (join ordering)
- CI/CD pipeline stage ordering
- Recursive Deps β dependency resolution via DFS
- Detect Cycle β prerequisite: graph must be a DAG
- DFS β the underlying traversal
- Shortest Path β longest path in DAG uses topological sort