Topological order applications
Dependency problems all reduce to topological sorting: course schedules, build systems, task pipelines, alien alphabets and package installs, each with its own way of turning the input into arrows.
Learning goals
- Model a dependency problem as a graph and choose the direction of the arrows
- Return a valid order, or report that none exists
- Derive ordering constraints from sorted words
- Compute the earliest finish time when independent tasks run in parallel
Prerequisites
Most real uses of topological sort begin with a different input: a list of prerequisites, a map of build targets, a sorted word list. The skill is turning that input into the right graph, and then reading the answer back.
Course schedule
Each course is a vertex. A prerequisite pair [a, b] (take b before a) becomes the arrow b → a. All courses can be finished exactly when the graph has no cycle, and a topological order is a valid schedule. The arrow direction is a common slip: reversing every arrow still gives a valid order of the *reverse* problem, so check with a tiny example.
The last preset in the animation, *Course schedule: 0 needs 1, 1 needs 2*, enters the arrows 2-1 and 1-0 and the order comes out 2 → 1 → 0.
1static int[] findOrder(int numCourses, int[][] prerequisites) { // pair {a, b}: b before a; empty array when impossible2List<List<Integer>> g = new ArrayList<>();3int[] indeg = new int[numCourses];4for (int i = 0; i < numCourses; i++) g.add(new ArrayList<>());5for (int[] p : prerequisites) { g.get(p[1]).add(p[0]); indeg[p[0]]++; } // arrow: prerequisite -> course6Deque<Integer> queue = new ArrayDeque<>();7for (int c = 0; c < numCourses; c++) if (indeg[c] == 0) queue.add(c);8int[] order = new int[numCourses];9int k = 0;10while (!queue.isEmpty()) {11int c = queue.poll();12order[k++] = c;13for (int next : g.get(c)) if (--indeg[next] == 0) queue.add(next);14}15return k == numCourses ? order : new int[0];16}
Topological sort: Kahn's algorithm and reversed DFS finish order
Interactive · edit the inputdirected graph · 6 vertices · 6 edges
in=2
in=2
in=1
in=1
in=0
in=0
queue (front first)
in-degree
order
Count the arrows entering each vertex. The vertices with in-degree 0 have nothing that must precede them: [4, 5].
1List<Integer> kahn(List<List<Integer>> g) { // topological order; shorter than n on a cycle2int n = g.size(); int[] indeg = new int[n];3for (List<Integer> out : g) for (int v : out) indeg[v]++;4Deque<Integer> q = new ArrayDeque<>();5for (int v = 0; v < n; v++) if (indeg[v] == 0) q.add(v); // sources are ready6List<Integer> order = new ArrayList<>();7while (!q.isEmpty()) {8int u = q.poll(); order.add(u);9for (int v : g.get(u)) if (--indeg[v] == 0) q.add(v); // remove u's arrows10}11return order; // order.size() < n: a cycle remains12}13List<Integer> dfsOrder(List<List<Integer>> g) { // reversed DFS finish order; null on a cycle14int n = g.size(); int[] colour = new int[n];15Deque<Integer> post = new ArrayDeque<>();16for (int s = 0; s < n; s++) if (colour[s] == 0 && !visit(g, s, colour, post)) return null;17return new ArrayList<>(post);18}19boolean visit(List<List<Integer>> g, int u, int[] colour, Deque<Integer> post) {20colour[u] = 1;21for (int v : g.get(u)) {22if (colour[v] == 1) return false; // an arrow back into the stack: a cycle23if (colour[v] == 0 && !visit(g, v, colour, post)) return false;24}25colour[u] = 2; post.addFirst(u); // finished after everything it points to26return true;27}
Live state
sources
[4, 5]
Execution trace (dry run table)Show 14 stepsHide
| Step | Line | State | What happens |
|---|---|---|---|
| 1 | L3 | sources=[4, 5] | Count the arrows entering each vertex. The vertices with in-degree 0 have nothing that must precede them: [4, 5]. |
| 2 | L8 | u=4 order=[4] | Take 4 and append it to the order. Now remove its out-arrows: each head loses one incoming arrow. |
| 3 | L9 | u=4 v=0 indeg[v]=1 | 4 → 0: in-degree of 0 drops to 1. |
| 4 | L9 | u=4 v=1 indeg[v]=1 | 4 → 1: in-degree of 1 drops to 1. |
| 5 | L8 | u=5 order=[4, 5] | Take 5 and append it to the order. Now remove its out-arrows: each head loses one incoming arrow. |
| 6 | L9 | u=5 v=2 indeg[v]=0 | 5 → 2: in-degree of 2 drops to 0, so 2 is ready and joins the queue. |
| 7 | L9 | u=5 v=0 indeg[v]=0 | 5 → 0: in-degree of 0 drops to 0, so 0 is ready and joins the queue. |
| 8 | L8 | u=2 order=[4, 5, 2] | Take 2 and append it to the order. Now remove its out-arrows: each head loses one incoming arrow. |
| 9 | L9 | u=2 v=3 indeg[v]=0 | 2 → 3: in-degree of 3 drops to 0, so 3 is ready and joins the queue. |
| 10 | L8 | u=0 order=[4, 5, 2, 0] | Take 0 and append it to the order. Now remove its out-arrows: each head loses one incoming arrow. |
| 11 | L8 | u=3 order=[4, 5, 2, 0, 3] | Take 3 and append it to the order. Now remove its out-arrows: each head loses one incoming arrow. |
| 12 | L9 | u=3 v=1 indeg[v]=0 | 3 → 1: in-degree of 1 drops to 0, so 1 is ready and joins the queue. |
| 13 | L8 | u=1 order=[4, 5, 2, 0, 3, 1] | Take 1 and append it to the order. Now remove its out-arrows: each head loses one incoming arrow. |
| 14 | L11 | return=[4, 5, 2, 0, 3, 1] | Topological order: 4 → 5 → 2 → 0 → 3 → 1. All 6 vertices were placed, which also proves the graph is acyclic. Every arrow goes from an earlier vertex to a later one. |
Build dependencies
A build system has targets that depend on other targets: app needs lib and util; lib needs util. Draw an arrow from each dependency to the target that needs it; a topological order is a valid build sequence, and a cycle is a build error that the tool must report instead of looping.
Targets have names rather than numbers, so a map from name to dependencies replaces the arrays. Using a sorted map and sorted dependency lists makes the output deterministic, which matters for reproducible builds.
1static List<String> buildOrder(Map<String, List<String>> dependsOn) { // target -> the targets it needs; null on a cycle2Map<String, Integer> missing = new TreeMap<>(); // how many dependencies are not built yet3Map<String, List<String>> needs = new TreeMap<>(); // dependency -> the targets waiting for it4for (var e : dependsOn.entrySet()) {5missing.putIfAbsent(e.getKey(), 0);6for (String d : e.getValue()) { missing.putIfAbsent(d, 0); }7}8for (var e : dependsOn.entrySet())9for (String d : new TreeSet<>(e.getValue())) {10needs.computeIfAbsent(d, k -> new ArrayList<>()).add(e.getKey());11missing.merge(e.getKey(), 1, Integer::sum);12}13PriorityQueue<String> ready = new PriorityQueue<>();14for (var e : missing.entrySet()) if (e.getValue() == 0) ready.add(e.getKey());15List<String> order = new ArrayList<>();16while (!ready.isEmpty()) {17String t = ready.poll();18order.add(t);19for (String waiting : needs.getOrDefault(t, List.of()))20if (missing.merge(waiting, -1, Integer::sum) == 0) ready.add(waiting);21}22return order.size() == missing.size() ? order : null;23}
Topological sort: Kahn's algorithm and reversed DFS finish order
Interactive · edit the inputdirected graph · 6 vertices · 6 edges
in=2
in=2
in=1
in=1
in=0
in=0
queue (front first)
in-degree
order
Count the arrows entering each vertex. The vertices with in-degree 0 have nothing that must precede them: [4, 5].
1List<Integer> kahn(List<List<Integer>> g) { // topological order; shorter than n on a cycle2int n = g.size(); int[] indeg = new int[n];3for (List<Integer> out : g) for (int v : out) indeg[v]++;4Deque<Integer> q = new ArrayDeque<>();5for (int v = 0; v < n; v++) if (indeg[v] == 0) q.add(v); // sources are ready6List<Integer> order = new ArrayList<>();7while (!q.isEmpty()) {8int u = q.poll(); order.add(u);9for (int v : g.get(u)) if (--indeg[v] == 0) q.add(v); // remove u's arrows10}11return order; // order.size() < n: a cycle remains12}13List<Integer> dfsOrder(List<List<Integer>> g) { // reversed DFS finish order; null on a cycle14int n = g.size(); int[] colour = new int[n];15Deque<Integer> post = new ArrayDeque<>();16for (int s = 0; s < n; s++) if (colour[s] == 0 && !visit(g, s, colour, post)) return null;17return new ArrayList<>(post);18}19boolean visit(List<List<Integer>> g, int u, int[] colour, Deque<Integer> post) {20colour[u] = 1;21for (int v : g.get(u)) {22if (colour[v] == 1) return false; // an arrow back into the stack: a cycle23if (colour[v] == 0 && !visit(g, v, colour, post)) return false;24}25colour[u] = 2; post.addFirst(u); // finished after everything it points to26return true;27}
Live state
sources
[4, 5]
Execution trace (dry run table)Show 14 stepsHide
| Step | Line | State | What happens |
|---|---|---|---|
| 1 | L3 | sources=[4, 5] | Count the arrows entering each vertex. The vertices with in-degree 0 have nothing that must precede them: [4, 5]. |
| 2 | L8 | u=4 order=[4] | Take 4 and append it to the order. Now remove its out-arrows: each head loses one incoming arrow. |
| 3 | L9 | u=4 v=0 indeg[v]=1 | 4 → 0: in-degree of 0 drops to 1. |
| 4 | L9 | u=4 v=1 indeg[v]=1 | 4 → 1: in-degree of 1 drops to 1. |
| 5 | L8 | u=5 order=[4, 5] | Take 5 and append it to the order. Now remove its out-arrows: each head loses one incoming arrow. |
| 6 | L9 | u=5 v=2 indeg[v]=0 | 5 → 2: in-degree of 2 drops to 0, so 2 is ready and joins the queue. |
| 7 | L9 | u=5 v=0 indeg[v]=0 | 5 → 0: in-degree of 0 drops to 0, so 0 is ready and joins the queue. |
| 8 | L8 | u=2 order=[4, 5, 2] | Take 2 and append it to the order. Now remove its out-arrows: each head loses one incoming arrow. |
| 9 | L9 | u=2 v=3 indeg[v]=0 | 2 → 3: in-degree of 3 drops to 0, so 3 is ready and joins the queue. |
| 10 | L8 | u=0 order=[4, 5, 2, 0] | Take 0 and append it to the order. Now remove its out-arrows: each head loses one incoming arrow. |
| 11 | L8 | u=3 order=[4, 5, 2, 0, 3] | Take 3 and append it to the order. Now remove its out-arrows: each head loses one incoming arrow. |
| 12 | L9 | u=3 v=1 indeg[v]=0 | 3 → 1: in-degree of 1 drops to 0, so 1 is ready and joins the queue. |
| 13 | L8 | u=1 order=[4, 5, 2, 0, 3, 1] | Take 1 and append it to the order. Now remove its out-arrows: each head loses one incoming arrow. |
| 14 | L11 | return=[4, 5, 2, 0, 3, 1] | Topological order: 4 → 5 → 2 → 0 → 3 → 1. All 6 vertices were placed, which also proves the graph is acyclic. Every arrow goes from an earlier vertex to a later one. |
Task prerequisites
When each task has a duration and independent tasks run in parallel, the question changes to *how soon can everything finish?* The earliest finish of a task is its duration plus the latest earliest-finish among its prerequisites. Process the tasks in topological order so every prerequisite is computed first; the answer is the largest finish time.
This is the critical-path method used in project planning: the longest chain of dependent tasks decides the total time, however many workers you have. The same recurrence over a DAG is the *Longest path* option of the next lesson's animation.
1static int earliestFinish(int[] duration, int[][] before) { // before[i] = {a, b}: a must finish before b starts; -1 on a cycle2int n = duration.length;3List<List<Integer>> g = new ArrayList<>();4int[] indeg = new int[n], finish = new int[n];5for (int i = 0; i < n; i++) g.add(new ArrayList<>());6for (int[] e : before) { g.get(e[0]).add(e[1]); indeg[e[1]]++; }7Deque<Integer> queue = new ArrayDeque<>();8for (int i = 0; i < n; i++) if (indeg[i] == 0) { queue.add(i); finish[i] = duration[i]; }9int done = 0, total = 0;10while (!queue.isEmpty()) {11int u = queue.poll();12done++;13total = Math.max(total, finish[u]);14for (int v : g.get(u)) {15finish[v] = Math.max(finish[v], finish[u] + duration[v]); // v starts after its slowest prerequisite16if (--indeg[v] == 0) queue.add(v);17}18}19return done == n ? total : -1;20}
Dynamic programming over a topological order: longest, shortest, count of paths
Interactive · edit the inputdirected graph · 6 vertices · 9 edges
d=0
topological order
best[v]
A DAG has a topological order: 0 → 1 → 2 → 3 → 5 → 4. Processing vertices in that order means every arrow into a vertex has been handled before the vertex is used, so its value is final. The source starts at 0.
1long[] dagBest(int n, List<int[]>[] adj, int[] order, int s, boolean longest) { // adj[u] = {v, weight}2long NONE = longest ? Long.MIN_VALUE : Long.MAX_VALUE;3long[] best = new long[n]; Arrays.fill(best, NONE);4best[s] = 0;5for (int u : order) { // topological order: best[u] is final when u is reached6if (best[u] == NONE) continue; // not reachable from s7for (int[] e : adj[u]) {8long cand = best[u] + e[1];9if (best[e[0]] == NONE || (longest ? cand > best[e[0]] : cand < best[e[0]])) best[e[0]] = cand;10}11}12return best;13}14long[] countPaths(int n, List<int[]>[] adj, int[] order, int s) {15long[] ways = new long[n]; ways[s] = 1;16for (int u : order) for (int[] e : adj[u]) ways[e[0]] += ways[u]; // paths into u extend along every arrow out of u17return ways;18}
Live state
order
[0, 1, 2, 3, 5, 4]
source
0
Execution trace (dry run table)Show 11 stepsHide
| Step | Line | State | What happens |
|---|---|---|---|
| 1 | L3 | order=[0, 1, 2, 3, 5, 4] source=0 | A DAG has a topological order: 0 → 1 → 2 → 3 → 5 → 4. Processing vertices in that order means every arrow into a vertex has been handled before the vertex is used, so its value is final. The source starts at 0. |
| 2 | L9 | u=0 v=1 cand=5 best[v]=5 | 0 → 1: 0 + 5 = 5. That beats the old value, so best[1] = 5. |
| 3 | L9 | u=0 v=2 cand=3 best[v]=3 | 0 → 2: 0 + 3 = 3. That beats the old value, so best[2] = 3. |
| 4 | L9 | u=1 v=3 cand=11 best[v]=11 | 1 → 3: 5 + 6 = 11. That beats the old value, so best[3] = 11. |
| 5 | L9 | u=1 v=2 cand=7 best[v]=7 | 1 → 2: 5 + 2 = 7. That beats the old value, so best[2] = 7. |
| 6 | L9 | u=2 v=4 cand=11 best[v]=11 | 2 → 4: 7 + 4 = 11. That beats the old value, so best[4] = 11. |
| 7 | L9 | u=2 v=5 cand=9 best[v]=9 | 2 → 5: 7 + 2 = 9. That beats the old value, so best[5] = 9. |
| 8 | L9 | u=2 v=3 cand=14 best[v]=14 | 2 → 3: 7 + 7 = 14. That beats the old value, so best[3] = 14. |
| 9 | L9 | u=3 v=5 cand=15 best[v]=15 | 3 → 5: 14 + 1 = 15. That beats the old value, so best[5] = 15. |
| 10 | L9 | u=3 v=4 cand=15 best[v]=15 | 3 → 4: 14 + 1 = 15. That beats the old value, so best[4] = 15. |
| 11 | L11 | return=[0, 5, 7, 14, 15, 15] arrows relaxed=9 | Values: [0, 5, 7, 14, 15, 15] ('-' = not reachable). Every arrow was used exactly once, so the whole computation is O(n + m). The longest path ends at 4 with length 15: 0 → 1 → 2 → 3 → 4 (read back through the parent links). |
Alien dictionary
Given words sorted in an unknown alphabet, compare each neighbouring pair: the first position where they differ shows that one letter comes before another. That is one arrow. If no position differs and the first word is longer, the list is invalid, since a word cannot precede its own prefix. A topological order of the letters is a valid alphabet; a cycle means the list contradicts itself.
Only neighbouring pairs matter, because sortedness is transitive. Letters that are never compared can appear anywhere that respects the arrows, so many answers can be valid. The animation lists the rules it finds, then runs Kahn's algorithm taking the smallest letter first.
1static String alienOrder(String[] words) { // "" when the list cannot be sorted by any alphabet2Map<Character, Set<Character>> out = new TreeMap<>();3Map<Character, Integer> indeg = new TreeMap<>();4for (String w : words) for (char c : w.toCharArray()) { out.putIfAbsent(c, new TreeSet<>()); indeg.putIfAbsent(c, 0); }5for (int i = 0; i + 1 < words.length; i++) {6String a = words[i], b = words[i + 1];7int k = 0, m = Math.min(a.length(), b.length());8while (k < m && a.charAt(k) == b.charAt(k)) k++;9if (k == m) { if (a.length() > b.length()) return ""; continue; } // a longer word before its own prefix10if (out.get(a.charAt(k)).add(b.charAt(k))) indeg.merge(b.charAt(k), 1, Integer::sum); // the first difference decides11}12PriorityQueue<Character> ready = new PriorityQueue<>();13for (var e : indeg.entrySet()) if (e.getValue() == 0) ready.add(e.getKey());14StringBuilder order = new StringBuilder();15while (!ready.isEmpty()) {16char c = ready.poll();17order.append(c);18for (char d : out.get(c)) if (indeg.merge(d, -1, Integer::sum) == 0) ready.add(d);19}20return order.length() == indeg.size() ? order.toString() : "";21}
Alien dictionary: letter order from sorted words
Interactive · edit the inputletters: an arrow x → y means x comes before y · 4 vertices · 0 edges
words
4 words use the letters a b c d. Two neighbouring words reveal at most one rule: the first position where they differ.
1String alienOrder(String[] words) { // "" when no order exists2Map<Character, Set<Character>> out = new TreeMap<>();3Map<Character, Integer> indeg = new TreeMap<>();4for (String w : words) for (char c : w.toCharArray()) { out.putIfAbsent(c, new TreeSet<>()); indeg.putIfAbsent(c, 0); }5for (int i = 0; i + 1 < words.length; i++) {6String a = words[i], b = words[i + 1];7int k = 0, m = Math.min(a.length(), b.length());8while (k < m && a.charAt(k) == b.charAt(k)) k++;9if (k == m) { if (a.length() > b.length()) return ""; continue; } // a longer word before its own prefix10if (out.get(a.charAt(k)).add(b.charAt(k))) indeg.merge(b.charAt(k), 1, Integer::sum); // first difference decides11}12PriorityQueue<Character> ready = new PriorityQueue<>();13for (var e : indeg.entrySet()) if (e.getValue() == 0) ready.add(e.getKey());14StringBuilder order = new StringBuilder();15while (!ready.isEmpty()) {16char c = ready.poll(); order.append(c);17for (char d : out.get(c)) if (indeg.merge(d, -1, Integer::sum) == 0) ready.add(d);18}19return order.length() == indeg.size() ? order.toString() : "";20}
Live state
words
4
letters
abcd
Execution trace (dry run table)Show 10 stepsHide
| Step | Line | State | What happens |
|---|---|---|---|
| 1 | L4 | words=4 letters=abcd | 4 words use the letters a b c d. Two neighbouring words reveal at most one rule: the first position where they differ. |
| 2 | L9 | a=abc b=abd position=2 | "abc" before "abd": they first differ at position 2, so c comes before d. |
| 3 | L9 | a=abd b=bc position=0 | "abd" before "bc": they first differ at position 0, so a comes before b. |
| 4 | L9 | a=bc b=bd position=1 | "bc" before "bd": they first differ at position 1, so c comes before d (already known). |
| 5 | L13 | constraints=2 | Now run Kahn's algorithm on the letters. Ready letters (nothing must precede them): a c. |
| 6 | L17 | letter=a order=a | Place a. Letters that were waiting only for it become ready. |
| 7 | L17 | letter=b order=ab | Place b. Letters that were waiting only for it become ready. |
| 8 | L17 | letter=c order=abc | Place c. Letters that were waiting only for it become ready. |
| 9 | L17 | letter=d order=abcd | Place d. Letters that were waiting only for it become ready. |
| 10 | L20 | return=abcd | Letter order: abcd. Letters never compared with each other may be placed in any order that keeps the rules; here ties go to the smaller letter. |
Package dependencies
To install a package you first need its dependencies, then theirs. A DFS from the requested package that appends each package *after* its dependencies finish produces an install order that contains only the packages actually needed. A package that is reached again while still on the recursion stack means a circular dependency.
This is the finish-order method from before, started from one root instead of every vertex. Packages already installed or already visited are skipped, so a shared dependency is installed once.
1static List<String> installPlan(String target, Map<String, List<String>> deps) { // dependencies first; null on a cycle2List<String> plan = new ArrayList<>();3Map<String, Integer> state = new HashMap<>(); // absent: new, 1: being resolved, 2: done4return resolve(target, deps, state, plan) ? plan : null;5}6static boolean resolve(String pkg, Map<String, List<String>> deps, Map<String, Integer> state, List<String> plan) {7int s = state.getOrDefault(pkg, 0);8if (s == 2) return true;9if (s == 1) return false; // back to a package still being resolved: circular10state.put(pkg, 1);11for (String d : deps.getOrDefault(pkg, List.of()))12if (!resolve(d, deps, state, plan)) return false;13state.put(pkg, 2);14plan.add(pkg); // all of its dependencies are already in the plan15return true;16}
Topological sort: Kahn's algorithm and reversed DFS finish order
Interactive · edit the inputdirected graph · 6 vertices · 6 edges
in=2
in=2
in=1
in=1
in=0
in=0
queue (front first)
in-degree
order
Count the arrows entering each vertex. The vertices with in-degree 0 have nothing that must precede them: [4, 5].
1List<Integer> kahn(List<List<Integer>> g) { // topological order; shorter than n on a cycle2int n = g.size(); int[] indeg = new int[n];3for (List<Integer> out : g) for (int v : out) indeg[v]++;4Deque<Integer> q = new ArrayDeque<>();5for (int v = 0; v < n; v++) if (indeg[v] == 0) q.add(v); // sources are ready6List<Integer> order = new ArrayList<>();7while (!q.isEmpty()) {8int u = q.poll(); order.add(u);9for (int v : g.get(u)) if (--indeg[v] == 0) q.add(v); // remove u's arrows10}11return order; // order.size() < n: a cycle remains12}13List<Integer> dfsOrder(List<List<Integer>> g) { // reversed DFS finish order; null on a cycle14int n = g.size(); int[] colour = new int[n];15Deque<Integer> post = new ArrayDeque<>();16for (int s = 0; s < n; s++) if (colour[s] == 0 && !visit(g, s, colour, post)) return null;17return new ArrayList<>(post);18}19boolean visit(List<List<Integer>> g, int u, int[] colour, Deque<Integer> post) {20colour[u] = 1;21for (int v : g.get(u)) {22if (colour[v] == 1) return false; // an arrow back into the stack: a cycle23if (colour[v] == 0 && !visit(g, v, colour, post)) return false;24}25colour[u] = 2; post.addFirst(u); // finished after everything it points to26return true;27}
Live state
sources
[4, 5]
Execution trace (dry run table)Show 14 stepsHide
| Step | Line | State | What happens |
|---|---|---|---|
| 1 | L3 | sources=[4, 5] | Count the arrows entering each vertex. The vertices with in-degree 0 have nothing that must precede them: [4, 5]. |
| 2 | L8 | u=4 order=[4] | Take 4 and append it to the order. Now remove its out-arrows: each head loses one incoming arrow. |
| 3 | L9 | u=4 v=0 indeg[v]=1 | 4 → 0: in-degree of 0 drops to 1. |
| 4 | L9 | u=4 v=1 indeg[v]=1 | 4 → 1: in-degree of 1 drops to 1. |
| 5 | L8 | u=5 order=[4, 5] | Take 5 and append it to the order. Now remove its out-arrows: each head loses one incoming arrow. |
| 6 | L9 | u=5 v=2 indeg[v]=0 | 5 → 2: in-degree of 2 drops to 0, so 2 is ready and joins the queue. |
| 7 | L9 | u=5 v=0 indeg[v]=0 | 5 → 0: in-degree of 0 drops to 0, so 0 is ready and joins the queue. |
| 8 | L8 | u=2 order=[4, 5, 2] | Take 2 and append it to the order. Now remove its out-arrows: each head loses one incoming arrow. |
| 9 | L9 | u=2 v=3 indeg[v]=0 | 2 → 3: in-degree of 3 drops to 0, so 3 is ready and joins the queue. |
| 10 | L8 | u=0 order=[4, 5, 2, 0] | Take 0 and append it to the order. Now remove its out-arrows: each head loses one incoming arrow. |
| 11 | L8 | u=3 order=[4, 5, 2, 0, 3] | Take 3 and append it to the order. Now remove its out-arrows: each head loses one incoming arrow. |
| 12 | L9 | u=3 v=1 indeg[v]=0 | 3 → 1: in-degree of 1 drops to 0, so 1 is ready and joins the queue. |
| 13 | L8 | u=1 order=[4, 5, 2, 0, 3, 1] | Take 1 and append it to the order. Now remove its out-arrows: each head loses one incoming arrow. |
| 14 | L11 | return=[4, 5, 2, 0, 3, 1] | Topological order: 4 → 5 → 2 → 0 → 3 → 1. All 6 vertices were placed, which also proves the graph is acyclic. Every arrow goes from an earlier vertex to a later one. |
Practice ladder
Now try it yourself
Easy
- Find the Town Judge
Medium
- Course Schedule
- Course Schedule II
- Parallel Courses III
- Alien Dictionary
Advanced
- Build a Matrix With Conditions
- Longest Increasing Path in a Matrix
Key takeaways
- A prerequisite pair (a needs b) is the arrow b to a: draw the arrow from what must come first.
- No valid order means a cycle: report it instead of looping.
- Alien dictionary: the first differing letter of each neighbouring pair gives one arrow; a longer word before its own prefix is invalid.
- With durations, the finish time of a task is its duration plus its slowest prerequisite; the longest chain decides the total.