Pattern 15 of 24 · Graphs · 25 min

Topological Sort

Order the nodes of a directed graph so every edge points forward, and detect cycles when that is impossible.

Spot it:prerequisites / dependenciesbuild orderis there a cycle?infer an orderingJavaScript you'll use: Graphs and grids in JavaScript Stacks, queues and deques
Say it in 30 seconds
  • 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.

0 of 0 sections ticked
30-second answer

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

Spot it in the problem0 of 4

Tap the signals you can see in the problem.

RememberPrerequisites, dependencies, "must happen beforean orderpossible to finishdirectedcycleminimum number of roundsinferred

Pick your variant
  • 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

graph
0in 01in 12in 13in 04in 25in 26in 2
queue (0)
empty
order (0)
empty
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
  1. 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.
  2. 0 and 3 have in-degree 0: nothing has to come before them, so they start the queue.
  3. Take 0 from the queue and append it to the order. Everything that had to come before 0 is already placed.
  4. 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.
  5. The edge 0 → 1 is used up, so 1's in-degree drops to 0: every node it waited for is placed. Enqueue 1.
  6. Take 3 and append it to the order.
  7. 3 → 5: 5's in-degree drops to 0. Enqueue 5.
  8. Take 1 and append it to the order.
  9. 1 → 6: 6's in-degree drops to 1. Not ready yet.
  10. 1 → 2: 2's in-degree drops to 0. Enqueue 2.
  11. Take 5 and append it to the order.
  12. 5 → 6: 6's in-degree drops to 0. Enqueue 6.
  13. Take 2 and append it to the order.
  14. 2 → 4: 4's in-degree drops to 1. Not ready yet.
  15. Take 6 and append it to the order.
  16. 6 → 4: 4's in-degree drops to 0. Enqueue 4.
  17. Take 4 and append it to the order.
  18. 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

  1. Fix the edge direction

    u → v means "u before v". In Course Schedule, [a, b] means b before a, so the edge is b → a.

  2. Build the adjacency list and the in-degrees

    adj[u].push(v) and indegree[v]++. Include nodes without edges.

  3. Seed the queue

    Push every node with in-degree 0.

  4. Take, append, release

    Pop a node, append it, lower its neighbours' counts, push any that reach 0.

  5. Check for leftovers

    All V placed: a valid order. Otherwise return the "impossible" value.

Interactive

Course planner: Kahn's algorithm

Each badge counts the prerequisites still missing. Courses at 0 wait in the queue; taking one lowers the badges it points to.

IntroMathData strSystemsDiscreteDatabaseAlgoCapstone

Your turn: click any course whose badge shows 0.

Intro and Math have no prerequisites (in-degree 0), so the queue starts with them. Press Step, or click a ready course.

Edit prerequisites
Click a course to

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

graph
012345
path (call stack) (0)
empty
finished (0)
empty
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
  1. Every node starts white (not visited). Gray will mean "on the current path", black "finished, along with everything after it".
  2. Start a DFS from 0, the first white node.
  3. Colour 0 gray and push it on the path. It stays gray until every node reachable from it is finished.
  4. 0 → 1: 1 is white, so visit it. The search goes deep before it comes back to 0's other edges.
  5. Enter 1: gray.
  6. 1 → 3: 3 is white, visit it.
  7. Enter 3: gray.
  8. 3 → 4: 4 is white, visit it.
  9. Enter 4: gray.
  10. 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.
  11. 3 is finished: black, append it.
  12. 1 is finished: black, append it.
  13. 0 → 2: 2 is white, visit it.
  14. Enter 2: gray.
  15. 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.
  16. 2 is finished: black, append it.
  17. 0 is finished: black, append it.
  18. Back in the outer loop: 5 is still white, so start another DFS from it.
  19. Enter 5: gray.
  20. 5 → 4: 4 is black, skip it.
  21. 5 is finished: black, append it.
  22. 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

try-it.js
// 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)); // [] [] null

Complexity and JavaScript

Complexity

Read more · 120 words
AlgorithmTimeExtra space
Kahn's algorithmO(V + E): each node queued once, each edge decremented onceO(V + E)
DFS with three coloursO(V + E)O(V + E), plus recursion up to V deep
Kahn's, level by levelO(V + E)O(V + E)
Letter graph in Alien DictionaryO(C) for C characters in totalO(1): at most 26 letters, 26² edges

JavaScript you'll use

JavaScript refresher: Graphs and grids in JavaScript Stacks, queues and deques Map and Set

With 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 3

Score 2 out of 3 or better to mark this page complete.

1. Kahn's algorithm stops with 7 of 10 nodes in the order. What can you conclude about the other 3?

Practice problems

Solve these in the browser editor. Each runs against hidden tests; the step-by-step solution unlocks after a wrong submission.

Open these in the problem list →

Next: Union-find answers 'are these connected?' as edges arrive.