Topological Sort
Order the nodes of a directed graph so every edge points forward, and detect cycles when that is impossible.
- O(V + E)Time: each node queued once, each edge once
- O(V + E)Space for the adjacency list and in-degrees
- length === VOrder length equal to V means no cycle
Show what to say
“When a problem has prerequisites or must-happen-before rules and asks for an order, or whether it is possible, I run Kahn's algorithm: count in-degrees, queue the nodes at zero, and release their neighbours as each is placed. If fewer than V nodes come out, there is a cycle. It is O(V + E).”
Trade-offs
- Kahn's vs DFS: Kahn's is iterative with a one-line cycle check; DFS colours show which nodes are on or behind a cycle.
- Two colours vs three: a plain visited set cannot tell a gray node on the current path (a cycle) from a black one (harmless).
- Queue vs min-heap in Kahn's: a queue returns one valid order; swap in a min-heap for the smallest order in dictionary order.
Bring it up before they ask
Get the edge direction right (in Course Schedule [a, b] means b comes first, so the edge is b → a), include nodes with no edges, and return the impossible value, not a partial order, when there is a cycle.
A topological order lists a directed graph's nodes so every edge points forward. One exists exactly when there is no cycle. Kahn's algorithm keeps taking a node with nothing left before it. Kahn's and DFS are both O(V + E) and catch cycles.
When to reach for it
Tap the signals you can see in the problem.
RememberPrerequisites, dependencies, "must happen beforean orderpossible to finishdirectedcycleminimum number of roundsinferred
- Kahn's algorithm (BFS)
Reports a cycle whenfewer than V nodes are placed
course schedulealien dictionary - Kahn's, level by level
Reports a cycle whensame
parallel coursesbuild stages - DFS with three colours
Reports a cycle whenan edge reaches a gray node
course schedule IIcycle detection - Kahn's on reversed edges
Reports a cycle whenleftovers are on, or lead into, a cycle
eventual safe states
The core idea: start with something that has no prerequisites
Think of getting dressed: socks before shoes, shirt before jacket.
Read more · 94 words
Draw u → v for "u before v". You can always start with something that has nothing pointing at it, like the socks. Put it first, cross it off, and repeat. If you run out of such things while some remain, they are waiting on each other: a cycle.
Kahn's algorithm keeps a count of each node's unmet prerequisites (its in-degree) and queues the nodes at 0. Placing a node lowers the count of the nodes it points to; any that hit 0 join the queue.
The cycle check is one line: order.length === n.
Kahn's algorithm: take whatever has no prerequisites left
- placed
- 0
- n
- 7
Count every node's incoming edges. A node's in-degree is the number of nodes that must come before it and haven't been placed yet.
function topoSortKahn(n, edges) {
const adj = Array.from({ length: n }, () => []);
const indegree = new Array(n).fill(0);
for (const [from, to] of edges) {
adj[from].push(to);
indegree[to]++;
}
const queue = [];
for (let v = 0; v < n; v++) if (indegree[v] === 0) queue.push(v);
const order = [];
for (let head = 0; head < queue.length; head++) {
const u = queue[head];
order.push(u);
for (const v of adj[u]) {
indegree[v]--;
if (indegree[v] === 0) queue.push(v);
}
}
return order.length === n ? order : [];
}All steps as text
- Count every node's incoming edges. A node's in-degree is the number of nodes that must come before it and haven't been placed yet.
- 0 and 3 have in-degree 0: nothing has to come before them, so they start the queue.
- Take 0 from the queue and append it to the order. Everything that had to come before 0 is already placed.
- The edge 0 → 5 is used up, so 5's in-degree drops to 1. 5 still waits for 1 more node, so it can't be placed yet.
- The edge 0 → 1 is used up, so 1's in-degree drops to 0: every node it waited for is placed. Enqueue 1.
- Take 3 and append it to the order.
- 3 → 5: 5's in-degree drops to 0. Enqueue 5.
- Take 1 and append it to the order.
- 1 → 6: 6's in-degree drops to 1. Not ready yet.
- 1 → 2: 2's in-degree drops to 0. Enqueue 2.
- Take 5 and append it to the order.
- 5 → 6: 6's in-degree drops to 0. Enqueue 6.
- Take 2 and append it to the order.
- 2 → 4: 4's in-degree drops to 1. Not ready yet.
- Take 6 and append it to the order.
- 6 → 4: 4's in-degree drops to 0. Enqueue 4.
- Take 4 and append it to the order.
- The queue is empty and all 7 nodes are placed. Each node entered the queue only after all its prerequisites, so every edge points forward in the order.
Play with it
Each course wears a badge counting its missing prerequisites.
Read more · 102 words
Press Step to run Kahn's, then press Make a cycle and watch the queue run dry.
Step by step
Fix the edge direction
u → vmeans "u before v". In Course Schedule,[a, b]means b before a, so the edge isb → a.Build the adjacency list and the in-degrees
adj[u].push(v)andindegree[v]++. Include nodes without edges.Seed the queue
Push every node with in-degree 0.
Take, append, release
Pop a node, append it, lower its neighbours' counts, push any that reach 0.
Check for leftovers
All V placed: a valid order. Otherwise return the "impossible" value.
Kahn's algorithm
Template 1: Kahn's algorithm
The default. It is iterative, builds the order directly, and checks for a cycle with a length comparison.
Read more · 25 words
It solves Course ScheduleMedium, Course Schedule IIMedium and Alien DictionaryHard.
Most graphs have many valid orders; Kahn's returns one. For the smallest order, swap the queue for a min-heap.
// Template 1: Kahn's algorithm. Nodes are 0..n-1 and each edge [from, to] means "from must come before to".
// Repeatedly take a node with no remaining incoming edges. Returns a topological order, or [] if there is a cycle.
export function topoSortKahn(n, edges) {
const adj = Array.from({ length: n }, () => []);
const indegree = new Array(n).fill(0);
for (const [from, to] of edges) {
adj[from].push(to);
indegree[to]++;
}
const queue = [];
for (let v = 0; v < n; v++) if (indegree[v] === 0) queue.push(v); // nothing has to come before these
const order = [];
for (let head = 0; head < queue.length; head++) {
const u = queue[head];
order.push(u);
for (const v of adj[u]) {
indegree[v]--; // the edge u → v is used up
if (indegree[v] === 0) queue.push(v); // everything v waited for is now placed
}
}
return order.length === n ? order : []; // nodes on (or behind) a cycle never reach in-degree 0
}DFS colours and levels
Template 2: DFS with three colours
A node finishes only after everything reachable from it, so reversing the finishing order points every edge forward.
Read more · 56 words
To catch cycles, mark nodes in three states:
- white: not visited;
- gray: on the current path;
- black: finished.
An edge into a gray node goes back up the current path: a cycle. An edge into a black node is harmless.
Colours also solve Find Eventual Safe StatesMedium: a node is safe when its DFS finishes black without meeting gray.
DFS with three colours: finishing order and back edges
- finished
- 0
Every node starts white (not visited). Gray will mean "on the current path", black "finished, along with everything after it".
function topoSortDfs(n, adj) {
const WHITE = 0, GRAY = 1, BLACK = 2;
const color = new Array(n).fill(WHITE);
const finished = [];
function visit(u) {
color[u] = GRAY;
for (const v of adj[u]) {
if (color[v] === GRAY) return false;
if (color[v] === WHITE && !visit(v)) return false;
}
color[u] = BLACK;
finished.push(u);
return true;
}
for (let u = 0; u < n; u++)
if (color[u] === WHITE && !visit(u)) return [];
return finished.reverse();
}All steps as text
- Every node starts white (not visited). Gray will mean "on the current path", black "finished, along with everything after it".
- Start a DFS from 0, the first white node.
- Colour 0 gray and push it on the path. It stays gray until every node reachable from it is finished.
- 0 → 1: 1 is white, so visit it. The search goes deep before it comes back to 0's other edges.
- Enter 1: gray.
- 1 → 3: 3 is white, visit it.
- Enter 3: gray.
- 3 → 4: 4 is white, visit it.
- Enter 4: gray.
- Every edge out of 4 is done, so everything that must come after 4 has already finished. Colour it black and append it to finished.
- 3 is finished: black, append it.
- 1 is finished: black, append it.
- 0 → 2: 2 is white, visit it.
- Enter 2: gray.
- 2 → 3: 3 is already black, so it and everything after it are finished. That's not a cycle, just two routes to the same node: skip it.
- 2 is finished: black, append it.
- 0 is finished: black, append it.
- Back in the outer loop: 5 is still white, so start another DFS from it.
- Enter 5: gray.
- 5 → 4: 4 is black, skip it.
- 5 is finished: black, append it.
- Reverse finished to get [5, 0, 2, 1, 3, 4]. Every node finished after all the nodes it points to, so in reverse every edge points forward.
// Template 2: DFS with three colours. WHITE = not visited, GRAY = on the current path, BLACK = finished.
// An edge into a GRAY node closes a cycle. A node finishes after everything it points to, so the reversed
// finishing order is a topological order. Edges [from, to] mean "from before to". Returns [] on a cycle.
export function topoSortDfs(n, edges) {
const adj = Array.from({ length: n }, () => []);
for (const [from, to] of edges) adj[from].push(to);
const WHITE = 0;
const GRAY = 1;
const BLACK = 2;
const color = new Array(n).fill(WHITE);
const finished = [];
function visit(u) { // false if a cycle is reachable from u
color[u] = GRAY;
for (const v of adj[u]) {
if (color[v] === GRAY) return false; // back edge: v is still on the path
if (color[v] === WHITE && !visit(v)) return false; // BLACK: already finished, nothing to do
}
color[u] = BLACK;
finished.push(u); // everything after u is already in `finished`
return true;
}
for (let u = 0; u < n; u++) {
if (color[u] === WHITE && !visit(u)) return [];
}
return finished.reverse();
}Template 3: Kahn's, level by level
When independent tasks run in parallel, process the queue in rounds: everything at in-degree 0 right now is one round.
Read more · 31 words
The number of rounds is the minimum number of semesters: Parallel CoursesMedium.
Run Kahn's on the reversed graph and every peeled node can't reach a cycle: the other way to solve Find Eventual Safe StatesMedium.
// Template 3: Kahn's algorithm one round at a time. Each round takes every node whose in-degree is 0 right now,
// e.g. every course you can take this semester. Returns the rounds (arrays of nodes), or null on a cycle.
// The number of rounds is the number of nodes on the longest dependency chain.
export function topoLevels(n, edges) {
const adj = Array.from({ length: n }, () => []);
const indegree = new Array(n).fill(0);
for (const [from, to] of edges) {
adj[from].push(to);
indegree[to]++;
}
let round = [];
for (let v = 0; v < n; v++) if (indegree[v] === 0) round.push(v);
const rounds = [];
let placed = 0;
while (round.length > 0) {
rounds.push(round);
placed += round.length;
const next = [];
for (const u of round) {
for (const v of adj[u]) {
indegree[v]--;
if (indegree[v] === 0) next.push(v); // its last prerequisite was in this round
}
}
round = next;
}
return placed === n ? rounds : null;
}Try it
// Edges are [from, to]: "from must come before to". Edit them and press Run.
const edges = [[0, 2], [1, 2], [2, 3], [1, 4], [4, 3]];
console.log(topoSortKahn(5, edges)); // [0, 1, 2, 4, 3]
console.log(topoSortDfs(5, edges)); // [1, 4, 0, 2, 3]: a different order, just as valid
console.log(topoLevels(5, edges)); // [[0, 1], [2, 4], [3]]: three rounds
const loop = [[0, 1], [1, 2], [2, 0]];
console.log(topoSortKahn(3, loop), topoSortDfs(3, loop), topoLevels(3, loop)); // [] [] nullComplexity and JavaScript
Complexity
Read more · 120 words
| Algorithm | Time | Extra space |
|---|---|---|
| Kahn's algorithm | O(V + E): each node queued once, each edge decremented once | O(V + E) |
| DFS with three colours | O(V + E) | O(V + E), plus recursion up to V deep |
| Kahn's, level by level | O(V + E) | O(V + E) |
| Letter graph in Alien Dictionary | O(C) for C characters in total | O(1): at most 26 letters, 26² edges |
JavaScript you'll use
JavaScript refresher: Graphs and grids in JavaScript Stacks, queues and deques Map and SetWith string nodes, don't count an edge twice in the in-degree while the Set stores it once: you will report a phantom cycle.
const adj = Array.from({ length: n }, () => []);
const indegree = new Array(n).fill(0);
for (const [from, to] of edges) { adj[from].push(to); indegree[to]++; }
const queue = [];
for (let v = 0; v < n; v++) if (indegree[v] === 0) queue.push(v);
for (let head = 0; head < queue.length; head++) { /* queue[head] is the next node of the order */ }
const after = new Map(); // letter → Set of letters that come after it
if (!after.get(a).has(b)) { after.get(a).add(b); indegree.set(b, indegree.get(b) + 1); }Interview questions
Kahn's algorithm or DFS?
Kahn's by default: iterative, a one-line cycle check, rounds for free. DFS colours show which nodes are on a cycle.
How would you return the cycle itself?
Three-colour DFS with a parent array. When an edge hits a gray node, follow parents back to it.
When is the topological order unique?
When Kahn's queue never holds more than one node.
Dependencies arrive one at a time. How do you spot a cycle at once?
A new edge u → v closes a cycle exactly when v already reaches u: search from v.
Tasks have durations. How long does the whole project take?
In topological order, finish[v] = duration[v] + max(finish[u]) over prerequisites u. The largest finish is the critical path.
Check yourself
Question 1 of 3Score 2 out of 3 or better to mark this page complete.
Practice problems
Solve these in the browser editor. Each runs against hidden tests; the step-by-step solution unlocks after a wrong submission.
- Course ScheduleDecide whether every course can be completed when some courses must be taken before others.Medium
- Course Schedule IIReturn an order in which to take all the courses so that every prerequisite comes first, or [] if none exists.Medium
- Parallel CoursesTake any number of courses per semester once their prerequisites are done; find the fewest semesters needed, or -1.Medium
- Find Eventual Safe StatesList the nodes of a directed graph from which every walk is guaranteed to stop at a dead end.Medium
- Alien DictionaryRecover an alphabet's letter order from a list of words sorted in that alphabet, or report that none exists.Hard