Topological Sort

Graphs
avg O(V + E)

Topological sort answers one question: in what order should I do these tasks, when some tasks depend on others? It takes a directed graph of dependencies and produces a list where every arrow points forward - every prerequisite comes before the things that need it. Build tools, package managers, and spreadsheets run this algorithm every day without you noticing. And if the dependencies form a cycle, no valid order exists - the algorithm tells you that too, for free.

Watch the pipeline get scheduled

Press play - or drag the timeline and step through it yourself.
Placed 1Placed 0Arrows removed 0Step 1 / 25
ABCDEFG

A build pipeline with 7 jobs. An arrow like A -> B means A must finish before B can start - think install -> compile -> test. Find an order that runs every job after its prerequisites.

ready (no prerequisites left) being placed placed in the order dependency satisfied

Complexity

Best
O(V + E)
Input already in order
Average
O(V + E)
Normal mixed input
Worst
O(V + E)
Worst possible input
Space
O(V)
Extra memory used

Getting dressed in the morning

Socks must go on before shoes. Your shirt must go on before your jacket. But socks versus shirt? Nobody cares - either order is fine. Getting dressed is a dependency graph: a few 'must come before' rules, and freedom everywhere else. A topological sort is any dressing order that breaks no rule, so shoes never come before socks. And if your rules ever form a loop - the belt requires the jacket, and the jacket requires the belt - there is no valid way to get dressed at all.

How it works, step by step

  1. Count the incoming arrows of every node - its in-degree. That is how many prerequisites it still waits for.

  2. Put every node with in-degree 0 into a ready queue. Nothing blocks them.

  3. Take a node out of the queue and append it to the order. It is now scheduled.

  4. Remove its outgoing arrows: each target's in-degree drops by 1. Any target that reaches 0 has all prerequisites met - it joins the queue.

  5. Repeat until the queue is empty. Every node and edge is touched once - O(V + E).

  6. If the finished order holds all V nodes, you have a valid schedule. If it is shorter, the leftover nodes sit in (or behind) a cycle - no valid order exists.

The code, in JavaScript

The version from the animation: count prerequisites, then repeatedly take anything with zero left. The queue holds every task that is ready right now.

javascript
function topoSort(graph) {
  // graph: { node: [nodes it points to] }
  const indegree = {};
  for (const node in graph) indegree[node] ??= 0;
  for (const node in graph) {
    for (const next of graph[node]) {
      indegree[next] = (indegree[next] ?? 0) + 1;
    }
  }

  // Start with every node that has no prerequisites.
  const queue = Object.keys(indegree).filter((n) => indegree[n] === 0);
  const order = [];

  while (queue.length > 0) {
    const node = queue.shift();
    order.push(node);                  // safe: nothing blocks it anymore

    for (const next of graph[node] ?? []) {
      indegree[next] -= 1;             // one prerequisite done
      if (indegree[next] === 0) {
        queue.push(next);              // last blocker gone - ready
      }
    }
  }

  return order;
}

const pipeline = {
  A: ["B", "E"], B: ["F"], C: ["B", "D"], D: ["F"],
  E: ["G"], F: ["G"], G: [],
};

topoSort(pipeline);   // → ["A", "C", "E", "B", "D", "F", "G"]

Dry run: scheduling the 7-job pipeline

The same steps the visualizer plays, written as a table. Generated by running the real algorithm - not written by hand.

StepPlacedWhat happened
11Take A from the ready queue and place it. Order so far: A.
21Placing A satisfies A -> E. E has no remaining prerequisites - ready. Ready: [C, E].
32Take C from the ready queue and place it. Order so far: A, C.
42Placing C satisfies C -> B. B has no remaining prerequisites - ready. Ready: [E, B].
52Placing C satisfies C -> D. D has no remaining prerequisites - ready. Ready: [E, B, D].
63Take E from the ready queue and place it. Order so far: A, C, E.
74Take B from the ready queue and place it. Order so far: A, C, E, B.
85Take D from the ready queue and place it. Order so far: A, C, E, B, D.
95Placing D satisfies D -> F. F has no remaining prerequisites - ready. Ready: [F].
106Take F from the ready queue and place it. Order so far: A, C, E, B, D, F.
116Placing F satisfies F -> G. G has no remaining prerequisites - ready. Ready: [G].
127Take G from the ready queue and place it. Order so far: A, C, E, B, D, F, G.

Good choice when…

  • Build and task scheduling: compile targets, CI stages, package install order - run every job after the jobs it depends on.
  • Course-prerequisite problems: 'can I finish all courses, and in what order?' is Kahn's algorithm word for word.
  • Spreadsheet-style recalculation: when one cell changes, recompute its dependents in dependency order, each exactly once.
  • As a preprocessing step: with nodes in topological order, shortest or longest path in a DAG is a single O(V + E) sweep - that is how critical-path analysis works.

Bad choice when…

  • The graph is undirected - 'must come before' has no meaning without arrow directions.
  • Cycles are legitimate in your data (mutual recursion, feedback loops). You need strongly connected components first, then sort the condensed DAG.
  • You only need reachability or a shortest route, not an ordering - plain BFS or DFS answers that with less machinery.

Common mistakes

  • Only a DAG has a topological order. One cycle anywhere and the honest answer is 'impossible'. Kahn's detects this for free: if the finished order has fewer than V nodes, there is a cycle - no extra code, just count.
  • Many valid orders usually exist. A test that asserts one exact output is brittle - it breaks when someone reorders an object or swaps the queue for a stack. Assert the property instead: every edge's source appears before its target.
  • Need a deterministic, repeatable order? Use a min-heap instead of a plain queue and you get the lexicographically smallest valid order - handy when two machines must produce identical schedules.
  • In the DFS version, push the node AFTER visiting its neighbours (post-order), then reverse at the end. Pushing before the recursion looks almost identical, runs without errors, and silently returns a wrong order.

Topological Sort vs. its closest relatives

AlgorithmBestAverageWorstSpaceStable
Topological Sortthis pageO(V + E)O(V + E)O(V + E)O(V)-
Depth-First SearchO(V + E)O(V + E)O(V + E)O(V)-
Breadth-First SearchO(V + E)O(V + E)O(V + E)O(V)-
Union–Find (DSU)O(1)O(α(n))O(α(n))O(n)-