Pattern 17 of 20

Topological Sort

Order tasks with dependencies using indegrees and a queue - and detect when no valid order exists.

What topological sort is

A topological order of a directed graph lists nodes so that every edge points forward: if A must precede B, A appears before B. It exists exactly when the graph has no directed cycle - a cycle means mutually blocking requirements with no valid order at all. Orders are generally not unique; any answer respecting all edges is correct.

The interview workhorse is Kahn's algorithm: repeatedly take a node with no remaining prerequisites (indegree 0), emit it, and 'complete' it by decrementing its dependents' indegrees, enqueueing any that reach zero. It sorts and cycle-checks in one pass: if fewer than n nodes are emitted, the leftovers form or feed a cycle.

How to recognize it

Signals: 'prerequisites', 'build/task order', 'must come before', dependency resolution, or any 'is this set of constraints satisfiable' over ordered pairs. Course schedule is the canonical costume; build systems and spreadsheet formula evaluation are the real-world versions worth mentioning.

Sequencing questions where dependency chains determine timing also live here - 'minimum semesters needed' is the number of BFS layers in Kahn's processing, i.e. the longest chain of prerequisites.

Kahn's algorithm

Build an adjacency list (prerequisite -> dependents) and an indegree array in one pass over the edges. Seed a queue with all indegree-0 nodes - independent starting points. Loop: pop, append to the order, decrement each dependent, enqueue those hitting zero. Count what you emit.

pseudocode
build adj (pre -> list of dependents) and indegree[]
queue = all nodes with indegree 0
order = []
while queue not empty:
    node = queue.pop_front()
    order.append(node)
    for next in adj[node]:
        indegree[next] -= 1
        if indegree[next] == 0:
            queue.push_back(next)
return order if len(order) == n else CYCLE

The DFS alternative

Post-order DFS also topologically sorts: finish all of a node's descendants, then prepend the node (or reverse the finish order). Cycle detection needs three colors - unvisited, in-progress, done - where meeting an in-progress node means a back edge, hence a cycle. Kahn's is usually the safer interview pick: iterative, counting-based cycle detection, and it naturally yields the layer structure that 'minimum rounds' questions need.

Common pitfalls

Reversing edge direction when building the graph - 'a requires b' means edge b -> a (b unlocks a). Drawing one edge before coding prevents the whole class of bugs.

Forgetting the emitted-count check: without comparing against n, cyclic inputs return a silently truncated 'order'.

Seeding the queue with only one zero-indegree node when several exist - all of them start available.

Using plain DFS cycle detection with a single visited boolean - it cannot distinguish 'visited earlier, fine' from 'still on the current path, cycle'; the in-progress state is mandatory.

Assuming the order is unique or lexicographic - if a problem demands the smallest order, the queue becomes a min-heap, at O(log n) per operation.

Worked examples

1. Course schedule (can you finish?)medium

Given course count and prerequisite pairs [a, b] meaning 'b before a', determine whether all courses can be completed.

  1. Feasibility of an ordering under precedence constraints = 'is the dependency graph acyclic' - exactly what Kahn's counting answers.
  2. Build edges b -> a with indegree[a] += 1 per pair; enqueue all courses with no prerequisites.
  3. Process the queue, decrementing dependents and enqueueing new zeros, counting emissions.
  4. Finish iff count == numCourses; any shortfall means some courses wait on each other in a loop. O(V + E) time and space.
pseudocode
for (a, b) in prerequisites:
    adj[b].append(a); indegree[a] += 1
queue = courses with indegree 0
done = 0
while queue not empty:
    c = queue.pop_front(); done += 1
    for next in adj[c]:
        indegree[next] -= 1
        if indegree[next] == 0: queue.push_back(next)
return done == numCourses

Remember: 'Can all constraints be satisfied' is 'does the graph have a cycle' - and Kahn's answers by simply counting how many nodes ever become free.

2. Course schedule II (produce the order)medium

Same setup, but return one valid ordering of all courses, or an empty list if impossible.

  1. Identical machinery - the emitted sequence IS a topological order, since a node is only emitted after all its prerequisites were.
  2. Append each dequeued course to the result while processing.
  3. If the result's length falls short of numCourses, return empty - the truncated list is meaningless under a cycle.
  4. Note for the follow-up: multiple valid orders exist; ties in the queue are arbitrary unless the problem demands lexicographic order (then use a min-heap).
pseudocode
# same as course schedule, plus:
order = []
... on dequeue: order.append(course) ...
return len(order) == numCourses ? order : []

Remember: Kahn's emission sequence is the schedule itself - feasibility checking and order construction are the same loop.

3. Minimum height treesmedium

For a tree of n nodes, find every root that minimizes the tree's height.

  1. The best roots are the CENTERS of the tree - at most two of them. Rooting anywhere else adds the distance to a center on top.
  2. Find centers by peeling: repeatedly remove all current leaves (degree 1) simultaneously - like Kahn's with 'degree 1' playing the role of 'indegree 0' on an undirected tree.
  3. Each peeled layer strips one unit of eccentricity from every direction; what remains last - one or two nodes - is the center set.
  4. Layered removal, O(n) total. The transferable idea: process-and-release layering works beyond DAGs whenever 'ready' nodes free up their neighbors.
pseudocode
if n <= 2: return all nodes
leaves = nodes with degree 1
remaining = n
while remaining > 2:
    remaining -= len(leaves)
    next_leaves = []
    for leaf in leaves:
        neighbor = its remaining neighbor
        degree[neighbor] -= 1
        if degree[neighbor] == 1: next_leaves.append(neighbor)
    leaves = next_leaves
return leaves

Remember: Peel leaves layer by layer and the last survivors are the tree's centers - Kahn's zero-indegree idea transplanted onto undirected degree-1 nodes.

Check yourself

Try to answer before revealing - retrieving from memory is what makes it stick.

1When does a directed graph have a topological order?
  • Always
  • Exactly when it contains no directed cycle
  • Only when it is connected
  • Only when some node has indegree 0

A cycle's members each require another member first - no linear order can satisfy that. Acyclic guarantees at least one indegree-0 node exists at every step, so Kahn's never stalls before finishing.

2How does Kahn's algorithm detect a cycle?
  • It throws an exception on revisiting a node
  • By counting emitted nodes: fewer than n means the remainder never reached indegree 0 - they form or depend on a cycle
  • By detecting a repeated queue state
  • It cannot - DFS is required for cycle detection

Nodes on a cycle wait for each other forever, so their indegrees never hit zero and they are never enqueued. The emission count falling short of n is the cycle's fingerprint.

3For prerequisite pair [a, b] meaning 'b must come before a', which edge do you add?
  • a -> b
  • b -> a
  • Both directions
  • Neither - use indegrees only

Edges point from prerequisite to dependent: completing b 'unlocks' progress toward a, and indegree[a] counts a's unmet prerequisites. Reversing this is the most common implementation bug in the pattern.

4'Minimum number of semesters, taking any number of parallel courses' maps to what?
  • The total number of courses
  • The number of layers in Kahn's processing - i.e. the longest prerequisite chain
  • The number of indegree-0 nodes
  • n minus the number of edges

Each 'semester' completes everything currently free, unlocking the next layer - level-by-level BFS over the DAG. The layer count equals the longest dependency chain, the unavoidable minimum.

Frequently asked questions

Kahn's or DFS-based topological sort - which should I use?

Kahn's, by default: iterative (no stack-depth risk), cycle detection is a simple count, and its layers directly answer 'minimum rounds' variants. DFS post-order is elegant and worth knowing, but its three-color cycle detection is easier to fumble live.

Is the topological order unique?

Only when at every step exactly one node has indegree 0 (equivalently, the DAG has a Hamiltonian path). Otherwise many orders are valid; problems wanting a specific one (usually lexicographically smallest) turn the queue into a min-heap.

What if the graph is disconnected?

Nothing changes: Kahn's seeds the queue with every indegree-0 node across all components and interleaves them naturally. This is another small robustness edge over hand-rolled DFS solutions that only launch from one node.

Keep this pattern in memory

Reading it once won't stick. Sign in and CoderFlow will bring it back right before you'd forget it.

Start reviewing