Topological Sort
Order the vertices of a directed acyclic graph so that every edge points forward; exists iff the graph has no cycle.
Overview
A topological order of a directed graph is a sequence of all vertices such that for every edge u → v, u appears before v. It is the formal version of "do the prerequisites first". A graph has a topological order if and only if it is a DAG (Directed Acyclic Graph) — a single directed cycle makes the requirement impossible, since some vertex on the cycle would have to precede itself.
Two linear-time algorithms produce it. Kahn's Algorithm repeatedly removes vertices with in-degree zero (a BFS-flavoured, queue-based approach that also detects cycles by counting how many vertices were output). DFS Topological Sort runs a DFS and reverses the finish order (post-order), detecting cycles with a "currently on the recursion path" marker.
A DAG can have many valid orders; which one you get depends on tie-breaking. When the problem asks for the lexicographically smallest order, Kahn's with a min-heap instead of a queue does it in O((V + E) log V).
Intuition
A mental model before the formal terms.
Picture a university course catalogue: each arrow says "take this before that". Laying the courses out on a single timeline so that every arrow points right is a topological order. If some chain of prerequisites loops back on itself, no timeline exists — you can never take the first course.
Two ways to build the timeline: (1) keep taking any course with no outstanding prerequisites and cross it off, which is Kahn's algorithm; (2) pick a course, chase its prerequisites all the way down, and write each course down only after everything it depends on is written — that is DFS post-order. Read in reverse, the post-order lists dependencies first.
How it works
- Kahn: compute
indeg[v]for every vertex. Put all vertices withindeg == 0in a queue. Popu, append it to the order, and decrementindeg[v]for every edgeu → v; anyvthat reaches zero joins the queue. If fewer thannvertices were output, the leftover vertices form (or feed into) a cycle. - DFS: colour every vertex white. For each white vertex run DFS: mark grey on entry, recurse into white neighbours, and treat a grey neighbour as a cycle. On exit mark black and push the vertex to a list. The reverse of that list is a topological order.
- Both are
O(V + E). Kahn is iterative by nature and gives "levels" (all vertices with no remaining prerequisites can run in parallel); DFS is a few lines shorter and produces the order that DP on DAGs / longest-path computations usually want.
Why it works
Existence: a DAG always has a vertex with in-degree 0 (walk backwards along edges; since there is no cycle the walk must stop within n steps, at a source). Removing it leaves a smaller DAG, so induction builds the whole order. Conversely a cycle has no valid position for its first vertex.
Kahn outputs a vertex only after all its predecessors have been output (its in-degree only hits zero then), so every edge points forward.
DFS: when u finishes, every vertex reachable from u has already finished (either it was explored inside u's call or had finished before — a grey one would signal a cycle). So u finishes after all its successors; reversing finish order puts u before them.
Recognition
How to tell a problem wants this.
- Words like "prerequisite", "dependency", "must happen before", "build order", "task scheduling with constraints".
- The input is a set of ordering constraints and the question is whether they are consistent (cycle check) or asks for *any* consistent sequence.
- You need to compute a DP over a DAG (longest path, number of paths, earliest start time) — evaluate in topological order.
- Implicit orderings: characters derived from sorted words (alien dictionary), version numbers, compile units.
Interactive visualization
Play, step, change the input. ← → and space work too.
Showing the closely related Kahn's Algorithm visualization.
1indeg[v] = number of incoming edges2queue = [v for v in nodes if indeg[v] == 0]3while queue not empty:4 u = queue.popleft(); order.append(u)5 for v in neighbors(u):6 indeg[v] -= 17 if indeg[v] == 0: queue.append(v)8if len(order) < n: cycle detectedPseudocode
1indeg[v] = number of incoming edges for each v2queue = all v with indeg[v] == 03order = []4while queue not empty:5 u = queue.pop(); order.append(u)6 for v in adj[u]:7 indeg[v] -= 18 if indeg[v] == 0: queue.push(v)9if len(order) < n: cycle -> no topological order10return orderImplementations
1from collections import deque2 3 4def topological_sort(n: int, edges: list[list[int]]) -> list[int] | None:5 """Umbrella topic: Kahn's algorithm is the representative implementation.6 edges are [u, v] meaning u must come before v.7 Returns an order, or None when the graph contains a cycle."""81 · Build adjacency list and in-degrees9 adj: list[list[int]] = [[] for _ in range(n)]10 indeg = [0] * n11 for u, v in edges:12 adj[u].append(v)13 indeg[v] += 114 152 · Seed the queue with every in-degree-0 vertex16 queue = deque(v for v in range(n) if indeg[v] == 0)17 183 · Emit from the queue, decrement neighbours19 order: list[int] = []20 while queue:21 u = queue.popleft()22 order.append(u)23 for v in adj[u]:24 indeg[v] -= 125 if indeg[v] == 0:26 queue.append(v)27 284 · Every vertex emitted iff the graph is acyclic29 return order if len(order) == n else None- Topological sort is the umbrella topic; Kahn's BFS peeling is implemented as the representative — the DFS variant lives under dfs-topological-sort.
- The build loop unpacks each
[u, v]pair and counts in-degrees. deque(v for v in range(n) if indeg[v] == 0)seeds the queue from a generator in one line.popleft()is O(1); each neighbour's in-degree drops by one and the vertex enqueues at zero.len(order) == nis the cycle test — cycle vertices never reach in-degree 0 and are never emitted.
collections.dequegives O(1)popleft;list.pop(0)is O(n) and the classic Kahn performance bug.list[int] | None(PEP 604) is the modern optional-return annotation.- The stdlib offers
graphlib.TopologicalSorter(Python 3.9+), which raisesCycleErroron cycles — worth knowing, though interviews expect the manual version.
- Using
list.pop(0)as the dequeue — O(n) per operation. - Returning
[]instead ofNoneon a cycle, which is indistinguishable from sorting an empty graph. - Iterating
edgesasfor u, v in edgeswhen edges may contain weights — unpacking then raisesValueError.
- Queue choice: C++
std::queue, Pythoncollections.deque, JS/TS have no O(1) stdlib queue and use an array with a moving head pointer. - Failure signalling: C++
std::optional+nullopt, JS/TSnullin a union type, PythonNone— all chosen over "empty list", which is ambiguous for n = 0. - Python ships
graphlib.TopologicalSorterin the stdlib; the other three languages have no built-in topological sort.
Complexity
Lexicographically smallest order via min-heap: O((V + E) log V).
Compare growth rates in the Complexity Explorer →When to use — and when not to
- Scheduling with precedence constraints: build systems, course prerequisites, task pipelines, package installation.
- Checking whether a set of ordering constraints is consistent (no cycle).
- DP on a DAG (longest path, counting paths, critical path) — process vertices in topological order so every predecessor is finalised first.
- The graph may contain cycles and you still need an order of *something* — condense with Strongly Connected Components first, then topologically sort the condensation.
- Undirected graphs — there is no direction to respect; "ordering" questions there are usually about BFS layers or trees.
- Weighted shortest paths on a general graph — that is Dijkstra's Algorithm / Bellman-Ford, although on a DAG topological order gives shortest *and* longest paths in
O(V + E).
Alternatives
Common mistakes
- Building edges in the wrong direction (
prerequisite → coursevscourse → prerequisite) and reversing the meaning of the output. - Forgetting that vertices with no edges at all still belong in the order (initialise the queue with all zero in-degree vertices).
- Returning a partial order when a cycle exists instead of reporting failure — always compare
len(order)withn. - Expecting a unique answer; tests usually accept any valid order, but lexicographic-smallest variants require a heap.
Interview patterns
- Course Schedule I/II: can all courses be finished (cycle check) and in what order.
- Alien Dictionary: derive character constraints from adjacent words, then topologically sort the characters.
- Longest path / minimum time to finish all tasks in a DAG with durations: DP over topological order.
- Parallel scheduling: Kahn's "levels" give the minimum number of rounds.
- Choosing between BFS, DFS, Dijkstra and DPAdvanced
- Stack versus queueBeginner
- Recursion versus iterationIntermediate
- When space complexity mattersIntermediate
- Course ScheduleIntermediate
- Network Delay TimeAdvanced
- Number of IslandsIntermediate