Graphs · Graph Traversal Fundamentals

Topological Sort

Repeatedly peeling off nodes with no remaining unprocessed dependencies (in-degree zero), using a fixed tie-breaking rule wherever more than one such node exists at once, so that the resulting order is not just valid but the single deterministic canonical one.

  • 5 min read
  • 9 practice questions
  • 2 graded problems
On this page
  1. Description
  2. The approach
  3. A second flavor: rounds instead of a list
  4. Where you'll see it
  5. Which tool for which job?
  6. Common mistakes
  7. Check yourself
  8. Key takeaways
  9. Quick revise
  10. Practice

Description

Tasks with prerequisites, such as courses, build steps or package installs, need an order in which every task comes after everything it depends on. That's a topological order of a directed graph, and one exists exactly when the graph has no cycle. Usually there are many valid orders, so the problem has to say which one it wants. This skill covers Kahn's algorithm, how ties are broken, and the round-by-round version that counts semesters.

We'll go from simple to more technical:

  1. The pattern: Kahn's algorithm, taking ready tasks from a min-heap.
  2. A second flavor: rounds, where each round takes everything ready at once.
  3. Where topological order runs in production (builds, installs, schedulers).
  4. What breaks it: tie rules, a short result, and rounds that merge.

The approach

Java

List<Integer> topoSort(int n, List<List<Integer>> adj) {
    int[] indegree = new int[n];
    for (List<Integer> next : adj) for (int v : next) indegree[v]++;

    PriorityQueue<Integer> ready = new PriorityQueue<>();      // min-heap: smallest ready node first
    for (int v = 0; v < n; v++) if (indegree[v] == 0) ready.add(v);

    List<Integer> order = new ArrayList<>();
    while (!ready.isEmpty()) {
        int node = ready.poll();
        order.add(node);
        for (int next : adj.get(node)) {
            if (--indegree[next] == 0) ready.add(next);        // its last prerequisite just finished
        }
    }
    return order;                                               // shorter than n means there was a cycle
}

Python

import heapq


def topo_sort(n: int, adj: list[list[int]]) -> list[int]:
    indegree = [0] * n
    for targets in adj:
        for v in targets:
            indegree[v] += 1

    ready = [v for v in range(n) if indegree[v] == 0]
    heapq.heapify(ready)                         # min-heap: smallest ready node first

    order = []
    while ready:
        node = heapq.heappop(ready)
        order.append(node)
        for nxt in adj[node]:
            indegree[nxt] -= 1
            if indegree[nxt] == 0:               # its last prerequisite just finished
                heapq.heappush(ready, nxt)
    return order                                 # shorter than n means there was a cycle

Kahn's algorithm keeps a pool of "ready" nodes - in-degree zero, nothing left to wait for. Taking one removes its outgoing edges, which may make others ready. Any node taken has had all its prerequisites taken before it, so the order is valid. The pool's type decides which valid order: a min-heap always takes the smallest ready node, which produces the unique lexicographically smallest order. On 5->2, 5->0, 4->0, 4->1, 2->3, 3->1 the heap gives 4 5 0 2 3 1; a plain FIFO queue gives 4 5 2 0 3 1 - also valid, just different.

A second flavor: rounds instead of a list

Process the ready pool in rounds - everything ready now, then everything that became ready because of them - and the number of rounds is the minimum number of "semesters" needed if any number of independent tasks can run in parallel:

Java

int rounds = 0, taken = 0;
while (!ready.isEmpty()) {
    rounds++;
    int size = ready.size();                 // snapshot: only what's ready at the start of this round
    for (int i = 0; i < size; i++) {
        int u = ready.poll();
        taken++;
        for (int v : adj.get(u)) if (--indegree[v] == 0) ready.add(v);
    }
}
return taken == n ? rounds : -1;             // -1: a cycle - some tasks can never start

Python

rounds = taken = 0
while ready:
    rounds += 1
    for _ in range(len(ready)):          # range() reads the size once: only what's ready now
        u = ready.popleft()
        taken += 1
        for v in adj[u]:
            indegree[v] -= 1
            if indegree[v] == 0:
                ready.append(v)
result = rounds if taken == n else -1    # -1: a cycle - some tasks can never start

The round count equals the number of nodes on the longest dependency chain. It's the same level-by-level BFS shape as tree level order, including its classic bug: re-reading ready.size() in the loop condition lets newly ready nodes slip into the current round. (In Python, for _ in range(len(ready)) reads the size once; an inner while ready: has the bug.)

Where you'll see it

Build systems, package managers, database migration runners, spreadsheet recalculation and task schedulers all compute a topological order of their dependency graph first. The round-by-round version is how schedulers find the critical path - the minimum number of stages when independent jobs run in parallel. A fixed tie-break (like the min-heap) makes the order independent of how inputs happened to be listed, which matters for reproducible builds.

Which tool for which job?

You needUse
Any valid orderKahn's algorithm with a FIFO queue
The lexicographically smallest orderKahn's algorithm with a min-heap
The number of rounds (semesters, parallel stages)Kahn's algorithm level by level: the longest path
An order with no in-degree arrayDFS reverse postorder
Cycle detection at the same timecheck order.size() == n
Python, built ingraphlib.TopologicalSorter

Common mistakes

MistakeWhat happensDo this instead
Expecting FIFO to give the smallest order[2, 3, 0, 1] instead of [2, 0, 3, 1]a min-heap when the order is specified
Ignoring a short resulttasks on a cycle silently disappearcheck the length equals n
Re-reading the queue size within a roundrounds merge and get undercountedsnapshot the size first
DFS ordering on a deep chainstack overflowKahn's algorithm, or an iterative DFS

Check yourself

Try to answer before opening each one - recalling beats re-reading.

1. Kahn's algorithm with a FIFO queue is run twice on the same input. Can the outputs differ?

Show answer

No - it's deterministic. But its order depends on the order nodes and edges were listed, and it's often not the lexicographically smallest: on the lesson's example FIFO gives 4 5 2 0 3 1 while the min-heap gives 4 5 0 2 3 1. Both are valid.

2. The topological sort returns 4 nodes for a 6-node graph. What happened?

Show answer

The graph has a cycle. Nodes on a cycle (and any node that depends on one) never reach in-degree zero, so they're never taken. A result shorter than n is Kahn's way of saying "not a DAG".

3. Why does processing Kahn's algorithm in rounds give the minimum number of semesters?

Show answer

Round k takes every course whose prerequisites were all finished in earlier rounds - nothing can be taken sooner. The number of rounds equals the length (in nodes) of the longest prerequisite chain, and no schedule can beat that chain, since each course on it must wait for the one before.

Key takeaways

  • A topological order exists exactly when the directed graph has no cycle.
  • Kahn's algorithm repeatedly takes a node with no remaining prerequisites; a result shorter than n means a cycle.
  • The ready pool's type picks the order: a min-heap gives the lexicographically smallest (O(V log V + E)); a FIFO queue is deterministic but input-order dependent (O(V + E)).
  • Processing in rounds (with a size snapshot) counts the minimum number of parallel stages - the longest dependency chain.
  • DFS finish order, reversed, is also a topological order.

Quick revise

The whole skill on one page, for the night before an interview.

This page puts the lesson and the deep dive together. Skim it before practice or an interview, and go back to the full lesson for anything you don't recognize.

Pick the tool

You needUse
Any orderKahn's with FIFO
Smallest orderKahn's with a min-heap
Rounds / semesterslevel-by-level Kahn's
Pythongraphlib.TopologicalSorter
Deep chainsKahn's (no recursion)

Snippets worth knowing by heart

Java

PriorityQueue<Integer> ready = new PriorityQueue<>();
for (int v = 0; v < n; v++) if (indeg[v] == 0) ready.add(v);
while (!ready.isEmpty()) {
    int u = ready.poll(); order.add(u);
    for (int v : adj.get(u)) if (--indeg[v] == 0) ready.add(v);
}
if (order.size() < n) throw new IllegalStateException("cycle");

Python

from graphlib import TopologicalSorter, CycleError
order = list(TopologicalSorter({"app": {"lib"}, "lib": {"utils"}}).static_order())

The traps

  • No tie rule when the output is graded: a FIFO queue gives a valid order, but not the smallest. Use a min-heap.
  • A short result means a cycle: tasks on it silently disappear. Check that the order has n nodes.
  • Merged rounds: re-reading the queue size merges levels and undercounts rounds. Snapshot the size first.
  • DFS depth: a deep chain overflows the stack. Use Kahn's algorithm, or an iterative DFS.

Staff-level points, one line each

  • Three independent chains of 3 tasks: 1,680 valid orders. One chain: 1.
  • FIFO gave [2, 3, 0, 1] and a min-heap [2, 0, 3, 1] on the same DAG.
  • A DFS topological sort overflowed Java's stack on a 100,000-task chain; Kahn's ordered it.
  • Kahn's rounds equaled the longest path on 300 random DAGs. A cap per round makes it NP-hard in general.
  • graphlib: CycleError names the cycle; get_ready() gave batches [docs, utils], [lib], [app], [tests].

Say it in an interview

  • "A topological order exists exactly when there's no cycle, and fewer than n taken means a cycle."
  • "If the output is graded, I use a min-heap so ties go to the smallest."
  • "Semesters are Kahn's rounds, which is the longest dependency chain."
  • "Build systems keep a ready set and decrement in-degrees as tasks finish."

Practice

This skill comes with 9 multiple-choice questions that tell you which misconception a wrong answer reveals and 2 graded problems. Your answers are graded and your mastery is tracked, so you'll know when the skill holds up. Running code and saving progress need a free account.

Practice Topological Sort free

Topological Sort

MediumProCoding problemJava · Python

Given a directed acyclic graph (DAG) with n nodes (labeled 0 to n-1) and a list of directed edges, return a valid topological order. Since multiple valid orders can exist, return the unique lexicographically smallest one: whenever more than one node has no remaining unprocessed…

The full statement, the examples and the hidden test suite are part of AI Proof Engineers Pro.

Minimum Semesters to Finish All Courses

MediumProCoding problemJava · Python

There are n courses labeled 0 to n-1. An edge u v means course u must be completed in an earlier semester than course v. In one semester you may take any number of courses, as long as all their prerequisites were completed in earlier semesters.

The full statement, the examples and the hidden test suite are part of AI Proof Engineers Pro.

Practice Topological Sort free

No card needed. Lessons, practice questions and easy problems are free forever.

Free plan, no card · 7-day Pro trial · Cancel anytime