Graphs

Topological Sort Dependency Ordering

Order vertices of a directed acyclic graph so that every edge points forward β€” prerequisites before dependents, courses before advanced courses, tasks before the tasks that need them. Two algorithms: Kahn's BFS and DFS post-order.

Foundations Β· Array Β· Linked List Β· Stack Β· Queue Β· HashMap Β· Binary Tree Β· Graph Β· BFS Β· DFS Β· Topo Sort Β· Shortest Path Β· Sorting
01
Section One Β· Foundation

What is Topological Sort?

DAG (directed acyclic graph) A B C D E Topological Order A B C D E A,B before C,D before E every edge u→v: u appears before v O(V + E) only exists for DAGs (no cycles)

A topological sort produces a linear ordering of vertices in a directed acyclic graph (DAG) such that for every directed edge u→v, vertex u appears before vertex v in the ordering. It answers the question: "in what order should I process these tasks if some tasks depend on others?" The ordering is NOT unique — a DAG may have multiple valid topological orders. Topological sort only works on DAGs; if the graph has a cycle, no valid ordering exists (you can't put A before B and B before A simultaneously). There are two standard algorithms: Kahn's algorithm (BFS-based, using in-degrees) and DFS post-order (reverse the finish order). Both run in O(V + E). Topological sort appears in interviews as "Course Schedule" (LC 207/210), build dependency resolution, compilation order, and any problem where you need to process nodes in dependency order.

Analogy: Getting dressed in the morning. You must put on underwear before pants, socks before shoes, but shirt and pants are independent. A topological sort gives you one valid order: underwear β†’ pants β†’ shirt β†’ socks β†’ shoes. Another valid order: shirt β†’ underwear β†’ socks β†’ pants β†’ shoes. Both respect every "must come before" constraint. If you had a rule "pants before underwear AND underwear before pants," no valid ordering exists β€” that's a cycle.
02
Section Two Β· Mental Model

How It Thinks

A vertex with in-degree 0 has no prerequisites β€” it can be processed immediately
β–Ά
Kahn's algorithm starts with ALL in-degree-0 vertices in a queue β€” these are the "ready" tasks. Process one, reduce neighbours' in-degrees, add newly-ready ones to the queue.
When you finish DFS on a vertex (all descendants explored), it has no remaining dependencies below it
β–Ά
Add it to the result on DFS FINISH (post-order). Reverse the post-order β†’ topological order. Alternatively, push onto a stack and pop at the end.
If the graph has a cycle, Kahn's queue empties before all vertices are processed
β–Ά
If result.size() < V after Kahn's completes β†’ cycle detected. This gives cycle detection as a bonus β€” "Course Schedule I" (LC 207) is just "does topo sort succeed?"
In DFS, a cycle exists if you visit a vertex that's currently on the recursion stack (grey state)
β–Ά
Three-colour marking: WHITE = unvisited, GREY = in progress (on stack), BLACK = finished. Edge to GREY = back edge = cycle.
Topological sort ordering is NOT unique β€” parallel tasks can appear in any relative order
β–Ά
Kahn's produces a specific order determined by queue processing (often lexicographic-smallest if using a PriorityQueue). DFS produces a different valid order. Both are correct.
Both algorithms visit each vertex once and each edge once
β–Ά
Time: O(V + E). Space: O(V) for the in-degree array (Kahn's) or recursion stack (DFS). Both are optimal.
03
Section Three Β· Kahn's BFS

Kahn's Algorithm (BFS + In-Degree)

Kahn's algorithm works by repeatedly removing vertices with no incoming edges (in-degree 0). When you remove a vertex, decrement the in-degree of all its neighbours. If a neighbour's in-degree drops to 0, it's now "ready" β€” add it to the queue. Continue until the queue is empty. If all vertices were processed, the order is valid; otherwise, a cycle exists.

Pseudocode β€” Kahn's Algorithm
function kahnTopoSort(V, edges): // 1. Build adjacency list + compute in-degrees adj = adjacency list from edges inDegree = int[V] // count of incoming edges for each edge (u β†’ v): inDegree[v]++ // 2. Enqueue all vertices with in-degree 0 queue = [] for v = 0 to V - 1: if inDegree[v] == 0: queue.add(v) // 3. Process the queue order = [] while queue not empty: u = queue.poll() order.add(u) for each neighbour v of u: inDegree[v]-- if inDegree[v] == 0: queue.add(v) // 4. Cycle check if order.size < V: return "cycle exists!" return order // O(V + E)
Kahn's step-by-step on a 6-vertex DAG
Edges: 0β†’1, 0β†’2, 1β†’3, 2β†’3, 3β†’4, 3β†’5 Init: inDeg=[0,1,1,2,1,1] β†’ queue=[0] Process 0: decrement 1,2 β†’ inDeg=[_,0,0,2,1,1] β†’ queue=[1,2] Process 1: decrement 3 β†’ inDeg=[_,_,0,1,1,1] β†’ queue=[2] Process 2: decrement 3 β†’ inDeg=[_,_,_,0,1,1] β†’ queue=[3] Process 3: decrement 4,5 β†’ inDeg=[_,_,_,_,0,0] β†’ queue=[4,5] Process 4, 5: done β†’ order = [0, 1, 2, 3, 4, 5] βœ“ Order: [0, 1, 2, 3, 4, 5] 6 vertices processed β†’ no cycle Key insight: at each step, only in-degree-0 vertices are ready
Kahn's is the interview favourite:
  • It's iterative (no recursion stack overflow), gives the topological order directly, and detects cycles as a bonus (order.size() < V).
  • For "Course Schedule II" (LC 210), it's the cleanest solution.
04
Section Four Β· DFS Post-Order

DFS-Based Topological Sort

Run DFS from every unvisited vertex. When a vertex finishes (all descendants explored), push it onto a stack (or prepend to a list). The final stack order (top to bottom) is a valid topological order. Intuitively: a vertex finishes AFTER all its dependents finish β€” so reversing the finish order puts prerequisites first.

Pseudocode β€” DFS Topo Sort
function dfsTopoSort(V, adj): visited = boolean[V] stack = [] // result in reverse for v = 0 to V - 1: if !visited[v]: dfs(v) return stack.reversed() // or pop from stack function dfs(u): visited[u] = true for each neighbour v of u: if !visited[v]: dfs(v) stack.push(u) // add on FINISH (post-order)
Why post-order?:
  • In DFS, a vertex finishes after ALL its descendants have finished. If uβ†’v, then v finishes before u.
  • So in the post-order list, v appears before u β€” reversing gives u before v, which is topological order.
05
Section Five Β· Cycle Detection

Detecting Cycles in DAGs

A topological sort only exists for DAGs. If the graph has a cycle, we need to detect it and return "impossible." Both algorithms detect cycles naturally:

Kahn's (BFS)

Count processed vertices

If order.size() < V after the queue empties, some vertices never reached in-degree 0 β€” they're part of a cycle. This is the cleanest cycle detection for directed graphs.

DFS (3-colour)

Detect back edges

Mark vertices: WHITE (unvisited), GREY (in progress), BLACK (done). If DFS reaches a GREY vertex β†’ back edge β†’ cycle. This is the standard directed cycle detection.

DFS 3-Colour Cycle Detection
// 0 = WHITE, 1 = GREY, 2 = BLACK int[] colour = new int[V]; boolean hasCycle(int u) { colour[u] = 1; // GREY β€” in progress for (int v : adj[u]) { if (colour[v] == 1) return true; // back edge β†’ cycle! if (colour[v] == 0 && hasCycle(v)) return true; } colour[u] = 2; // BLACK β€” done return false; }
Course Schedule I (LC 207) = cycle detection.:
  • "Can you finish all courses?" = "Is the prerequisite graph a DAG?" Run Kahn's and check order.size() == numCourses.
  • If yes β†’ return true. If no β†’ cycle β†’ return false.
06
Section Six Β· Comparison

Kahn's vs DFS β€” Which to Use

Aspect Kahn's (BFS) DFS Post-Order
Approach Iterative (queue) Recursive (stack)
Cycle detection order.size() < V GREY→GREY back edge
Output Direct topological order Reverse of post-order
Stack overflow risk None (iterative) Yes, on deep graphs
Lexicographic order Easy: use PriorityQueue Harder to guarantee
Interview preference βœ“ More common, cleaner Option if DFS is already built
Time / Space O(V+E) / O(V) O(V+E) / O(V)
Interview default: Kahn's.:
  • It's iterative, gives the answer directly, and cycle detection is a one-line check.
  • Use DFS only when the problem already requires DFS traversal (e.g., "Alien Dictionary" where you build the graph AND topo-sort it).
07
Section Seven Β· When to Use

When to Reach for Topological Sort

Signal Pattern Example
"prerequisites" / "dependencies" / "ordering" Topo sort (Kahn's) Course Schedule I & II (LC 207, 210)
"can all tasks be completed?" Cycle detection via topo sort Course Schedule I (LC 207)
"return an ordering that satisfies all constraints" Kahn's or DFS topo sort Course Schedule II (LC 210)
"alien dictionary" / "infer order from comparisons" Build DAG from pairs β†’ topo sort Alien Dictionary (LC 269)
"parallel task scheduling" / "minimum time" Topo sort + level tracking (longest path in DAG) Parallel Courses (LC 1136)
"compilation order" / "build order" Topo sort on dependency graph Build system dependencies
08
Section Eight Β· Implementation

Build It Once

Build Kahn's once β€” it's the interview standard. The structure is always the same: build adjacency list, compute in-degrees, BFS from in-degree-0 vertices, check cycle.

Java β€” Course Schedule II (Kahn's)
// Course Schedule II β€” LC 210 β€” O(V + E) int[] findOrder(int numCourses, int[][] prereqs) { List<List<Integer>> adj = new ArrayList<>(); int[] inDeg = new int[numCourses]; for (int i = 0; i < numCourses; i++) adj.add(new ArrayList<>()); for (int[] p : prereqs) { adj.get(p[1]).add(p[0]); // prereq β†’ course inDeg[p[0]]++; } Deque<Integer> queue = new ArrayDeque<>(); for (int i = 0; i < numCourses; i++) if (inDeg[i] == 0) queue.offer(i); int[] order = new int[numCourses]; int idx = 0; while (!queue.isEmpty()) { int u = queue.poll(); order[idx++] = u; for (int v : adj.get(u)) if (--inDeg[v] == 0) queue.offer(v); } return idx == numCourses ? order : new int[0]; }
09
Section Nine Β· Java Reference

Use It In Java

IN JAVA β€” Topological Sort Patterns
Adjacency list List<List<Integer>> adj = new ArrayList<>(); β€” initialise n empty lists
In-degree array int[] inDeg = new int[n]; β€” increment for each incoming edge
Queue Deque<Integer> q = new ArrayDeque<>(); β€” Kahn's BFS queue
Kahn's Step Java Code Note
Build graph adj.get(u).add(v); inDeg[v]++; Read edge direction carefully (prereq→course)
Seed queue if (inDeg[i] == 0) q.offer(i); All vertices with no incoming edges
Process int u = q.poll(); order[idx++] = u; Take one ready vertex
Decrement if (--inDeg[v] == 0) q.offer(v); Pre-decrement + check in one line
Cycle check return idx == n ? order : new int[0]; Empty array = cycle detected
⚠ Gotcha: Edge direction matters! In "Course Schedule," prerequisites are given as [course, prereq] β€” the edge goes FROM prereq TO course (adj.get(prereq).add(course)). Reversing this produces a wrong answer. Always clarify: "X is a prerequisite of Y" means edge Xβ†’Y.
⚠ Gotcha: For lexicographically smallest topo order, replace ArrayDeque with PriorityQueue in Kahn's. The min-heap always picks the smallest ready vertex first. This changes nothing about correctness but guarantees a unique canonical ordering.
10
Section Ten Β· Practice

Problems To Solve

Topological sort appears in interviews as "Course Schedule" or a disguised dependency-ordering problem. The key skill is recognising "prerequisites = directed edges β†’ topo sort." Once recognised, Kahn's template is mechanical. The hard problems (Alien Dictionary, Parallel Courses) add a graph-construction step before the topo sort itself.

Difficulty Pattern Problem Key Insight
Medium cycle Course Schedule β€” LC 207 "Can you finish all courses?" = "Is the graph a DAG?" Run Kahn's, check order.size() == numCourses.
Medium topo Course Schedule II β€” LC 210 Return a valid ordering. Kahn's directly produces the answer. Return empty array if cycle.
Hard topo+build Alien Dictionary β€” LC 269 Build a character DAG from adjacent word pairs (first differing character = edge). Then Kahn's on the character graph. Edge case: "abc" before "ab" is invalid.
Medium topo+dp Parallel Courses β€” LC 1136 Topo sort with level tracking. Process layer by layer (like BFS levels). Number of layers = minimum semesters. If cycle β†’ return -1.
Medium topo Minimum Height Trees β€” LC 310 Not a classic topo sort but uses the in-degree peeling technique: repeatedly remove leaves (degree 1) until 1–2 nodes remain. Those are the roots of minimum-height trees.
Medium topo Longest Increasing Path in a Matrix β€” LC 329 Each cell is a node, edge from smaller to larger neighbour. Topo sort on this implicit DAG. Longest path = max level in topo order. Also solvable with DFS + memoisation.
Interview Pattern:
  • When you see "prerequisites," "dependencies," "ordering constraints," or "can all tasks be completed" β€” think topological sort.
  • Build the graph, run Kahn's, check for cycles.
  • The template is always: in-degree array β†’ seed queue β†’ BFS process β†’ check count == V.

β†’ See DFS for the recursive traversal foundation