Topological Sort

Mediumgraph · topological sort · kahn · dag · cycle detection · dependencies⏱ 6 min readUpdated

Order the nodes of a directed graph so every edge points forward, by repeatedly removing nodes with no remaining prerequisites (Kahn) or by DFS finish times.

PatternTopological Sort

Instinct Tasks with prerequisites, "X must come before Y", or an order hidden in comparisons: build a directed graph and peel off nodes with zero in-degree.

Memory hack No prereqs? Off you go. Each finished task frees its followers; if someone is never freed, there is a cycle.

At a glance

When to use it

  • Tasks or courses come with prerequisites or must-come-before rules
  • You must decide whether a valid order exists or return one
  • An order is hidden in comparisons, such as a sorted alien word list
  • You need to detect a cycle in a directed graph

Key idea

Draw an edge from each prerequisite to its dependent, then repeatedly take nodes with zero in-degree and decrement their followers (Kahn); every node and edge is processed once, so ordering and cycle detection cost O(V + E) instead of trying permutations.

Typical complexity

O(V + E) time, O(V + E) space

How to spot it If the problem says X must happen before Y or depends on Y and asks whether everything can be finished or in what order, think Topological Sort first.

The instinct

Whenever a problem says "before", "depends on", "prerequisite" or "must be built after", draw an arrow from the thing that must happen first to the thing that waits for it. You now have a directed graph. A valid schedule exists exactly when that graph has no cycle, and the schedule itself is a topological order.

The most interview-friendly way to compute it is Kahn's algorithm: count incoming arrows (in-degree) for every node, start with the nodes that have zero, and every time you "finish" a node, decrement its followers. Followers that reach zero join the queue. If you finish fewer than n nodes, the leftovers are trapped in a cycle.

Recognise it

Signal in the problem Why it points here
"can you finish all courses given prerequisite pairs" Feasibility = the dependency graph is acyclic
"return an order in which to take the courses / build the packages" Output the topological order itself
"derive the alphabet order from a sorted list of alien words" Adjacent words reveal "letter a before letter b" edges
"nodes from which every path ends at a terminal" (safe states) Reverse the edges and peel terminals, or DFS colouring
"trim leaves repeatedly to find the centre" Undirected cousin: peel degree-1 nodes layer by layer (Minimum Height Trees)
"parallel jobs, minimum number of semesters / total time" Kahn's layers = rounds; longest path in a DAG
"order items and groups both respecting dependencies" Two levels of topological sort (Sort Items by Groups Respecting Dependencies)

Not this pattern if...

  • The graph is undirected and you only need connectivity: DFS/BFS or Union-Find.
  • You need the shortest path by edge count without ordering constraints: BFS.
  • Edges are "a equals b" relations rather than "a before b": Union-Find.
  • The order is fully given and you only sort by a key: just sort.

Memory hack

Tip

No prereqs? Off you go. Picture students queueing for a ride where some must wait for friends to go first. Anyone with no one to wait for goes; each person who leaves lets their waiting friends count down. If some people still wait at closing time, they are waiting on each other: a cycle.

Analogy: getting dressed. Socks before shoes, underwear before trousers, trousers before belt. You start with anything that has no "before" (socks, underwear, shirt), and each item you put on unlocks the next. Trying to wear shoes before socks is a cycle you cannot satisfy.

Say it out loud in the interview:

  1. "Edge from prerequisite to dependent; I'll build adjacency lists and in-degrees."
  2. "Queue every zero in-degree node; pop, append to order, decrement neighbours, enqueue new zeros."
  3. "If the order has fewer than n nodes, there is a cycle, so the answer is impossible."

How it works

flowchart TD
  A["Build adjacency list and in-degree per node"] --> B["Enqueue all nodes with in-degree 0"]
  B --> C{"Queue empty?"}
  C -- no --> D["Pop u, append u to order"]
  D --> E["For each edge u to v: indegree of v minus 1"]
  E --> F{"indegree of v is 0?"}
  F -- yes --> G["Enqueue v"]
  F -- no --> C
  G --> C
  C -- yes --> H{"order size equals n?"}
  H -- yes --> I["Valid topological order"]
  H -- no --> J["Cycle: no valid order"]

DFS alternative. Colour nodes white (unvisited), grey (on the current path) and black (done). Reaching a grey node means a back edge, which is a cycle. Appending each node when it turns black produces a reverse topological order. This three-colour DFS is also the cleanest way to solve Find Eventual Safe States: a node is safe if its DFS finishes black without ever touching grey.

Building edges from comparisons (Alien Dictionary). Compare each pair of adjacent words and find the first differing letter; that gives one edge. If no letter differs and the first word is longer (like "abc" before "ab"), the input is invalid.

Java template

java
import java.util.*;

class TopoSort {
    // Kahn's algorithm. edges[i] = {before, after}. Returns empty array if a cycle exists.
    static int[] order(int n, int[][] edges) {
        List<List<Integer>> adj = new ArrayList<>();
        for (int i = 0; i < n; i++) adj.add(new ArrayList<>());
        int[] indeg = new int[n];
        for (int[] e : edges) { adj.get(e[0]).add(e[1]); indeg[e[1]]++; }

        Deque<Integer> q = new ArrayDeque<>();
        for (int i = 0; i < n; i++) if (indeg[i] == 0) q.offer(i);

        int[] out = new int[n];
        int idx = 0;
        while (!q.isEmpty()) {
            int u = q.poll();
            out[idx++] = u;                      // problem-specific: track layer, time, etc.
            for (int v : adj.get(u)) if (--indeg[v] == 0) q.offer(v);
        }
        return idx == n ? out : new int[0];
    }

    // Three-colour DFS: returns false on a cycle; post-order fills 'post' in reverse topo order.
    static boolean dfs(int u, List<List<Integer>> adj, int[] state, List<Integer> post) {
        state[u] = 1;                            // 1 = grey, on current path
        for (int v : adj.get(u)) {
            if (state[v] == 1) return false;     // back edge: cycle
            if (state[v] == 0 && !dfs(v, adj, state, post)) return false;
        }
        state[u] = 2;                            // 2 = black, fully explored
        post.add(u);
        return true;
    }
}

Course Schedule gives pairs as [course, prereq], so the edge is prereq → course; flip the indices accordingly.

Complexity

Step Time Space
Build graph and in-degrees O(V + E) O(V + E)
Kahn's processing O(V + E) O(V) queue
Three-colour DFS O(V + E) O(V) recursion
Alien Dictionary edge building O(total characters) O(1) alphabet-sized graph

Variations

  • Feasibility only: count processed nodes. Example: Course Schedule.
  • Produce the order: return the queue's output. Example: Course Schedule II.
  • Infer edges first: derive constraints from adjacent items. Example: Alien Dictionary.
  • Reverse-graph peeling / colouring: find nodes not on or leading to a cycle. Example: Find Eventual Safe States.
  • Leaf trimming on undirected trees: peel degree-1 nodes until 1-2 remain. Example: Minimum Height Trees.
  • Layered / two-level sort: count rounds, or sort groups then items within groups. Example: Sort Items by Groups Respecting Dependencies.
  • Lexicographically smallest order: replace the queue with a min-heap.

Common mistakes

Warning

Reversing the edge direction silently gives a "valid" but backwards order. Write down "edge = must-come-first → comes-later" before coding, especially when input pairs are [after, before].

  • Forgetting the cycle check (processed == n) and returning a partial order as if it were complete.
  • Using a plain visited boolean in DFS cycle detection; it cannot tell a back edge (cycle) from a cross edge to a finished node. Use three states.
  • Storing Alien Dictionary edges in a Set but incrementing in-degree on every comparison; duplicates then never drain to zero. Increment only when set.add returns true.
  • Missing nodes that appear in no edge: they still have in-degree 0 and must be in the output.
  • In Alien Dictionary, ignoring the invalid "longer word is a prefix-earlier" case.

All practice problems

6 problems on this site use this pattern, easiest first. Solve them in order and say the instinct out loud before you code.

My notes saved in this browser