Topological Sort — The Complete Guide
Master topological sorting from first principles. Learn to order tasks respecting dependencies, detect cycles in directed graphs, and confidently solve every dependency-based interview question.
Table of Contents
Intuition from Scratch
Why does this pattern exist?
Imagine you're getting dressed in the morning. You can't put on shoes before socks, and you can't put on a shirt before an undershirt. There are dependencies — some tasks must happen before others. Topological sort gives you a valid ordering of all tasks such that every dependency is respected.
More formally: given a Directed Acyclic Graph (DAG) where an edge from A → B means "A must come before B," topological sort produces a linear ordering of all nodes where every edge goes from left to right. If the graph has a cycle (A depends on B, B depends on C, C depends on A), no valid ordering exists — and topological sort detects this too.
The key constraint: the graph must be a DAG. "Directed" means edges have a direction (A → B ≠ B → A). "Acyclic" means no cycles. If there's a cycle, you can't order the nodes — it's like saying "A before B, B before C, C before A." Impossible.
Analogies to Build Intuition
University Course Prerequisites
To take Algorithms, you need Data Structures. To take Data Structures, you need Intro to Programming. Topological sort gives you a valid semester plan: Intro → DS → Algorithms. If two courses have a circular prerequisite, the system is broken — that's a cycle.
Building a House
You can't install windows before building walls, and you can't build walls before laying the foundation. Each task depends on others. Topological sort gives you a valid construction schedule where every prerequisite is completed before the task that needs it.
Package Manager (npm install)
When you install a package, it has dependencies, which have their own dependencies. The package manager does a topological sort to figure out the install order: install leaf dependencies first, then packages that depend on them, and so on up to your package.
What kind of problems does it solve?
Topological sort is your go-to when:
- You need to order tasks with dependencies (course schedule, build order)
- You need to detect cycles in a directed graph
- You need to find if a valid ordering exists given constraints
- You need to compute longest/shortest path in a DAG
- You need to propagate values through dependencies (compile order, evaluation order)
- You need to determine the minimum time to complete all tasks with parallel execution
Two Approaches — Kahn's (BFS) vs DFS
Kahn's Algorithm (BFS / In-degree approach): 1. Count in-degrees (number of incoming edges) for each node 2. Start with all nodes that have in-degree 0 (no dependencies) 3. Process them, reduce in-degrees of their neighbors 4. When a neighbor's in-degree hits 0, add it to the queue 5. If all nodes are processed → valid order. If not → cycle exists. DFS Post-Order approach: 1. Run DFS from each unvisited node 2. After visiting ALL descendants of a node, add it to a stack 3. The stack (reversed) gives the topological order 4. If you encounter a node that's "in progress" → cycle detected. Both produce valid topological orderings. Kahn's is more intuitive for most people and naturally gives the order left-to-right. DFS is more concise and useful when you need post-order processing.
The core insight
Topological sort is about respecting dependencies. A node can only be processed after ALL its prerequisites are done. Kahn's algorithm makes this explicit: a node enters the queue only when its in-degree drops to 0 (all prerequisites satisfied). If some nodes never reach in-degree 0, they're stuck in a cycle.
Pattern Recognition
Topological sort problems don't always say "topological sort." They hide behind words like "prerequisites," "dependencies," and "ordering." Here are the signals.
Keywords & Signals in the Problem Statement
Use Topological Sort when you see...
- ✅"Prerequisites" or "dependencies" between tasks
- ✅"Order of courses" or "build order"
- ✅"Can all tasks be completed?" (cycle detection)
- ✅"Find a valid ordering" respecting constraints
- ✅"Compile order" or "evaluation order"
- ✅Directed edges representing "must come before"
- ✅"Minimum semesters / time" to complete all courses
- ✅"Alien dictionary" — derive letter ordering from sorted words
- ✅"Parallel task scheduling" with dependencies
- ✅DAG + need to process nodes in dependency order
Don't use Topological Sort when...
- ❌The graph is undirected (topo sort requires directed edges)
- ❌You need shortest path with weights (use Dijkstra or Bellman-Ford)
- ❌The graph has cycles and you need to handle them (topo sort just detects them)
- ❌You need to find connected components (use Union-Find or DFS/BFS)
- ❌The problem is about trees (trees have a natural root-to-leaf order)
- ❌There are no dependency relationships between items
Topological Sort vs. Similar Patterns
| Pattern | When to Use | Key Difference |
|---|---|---|
| Topological Sort (Kahn's) | Order tasks with dependencies, detect cycles, parallel scheduling | BFS on DAG using in-degrees. Processes nodes level by level (useful for 'minimum time'). |
| Topological Sort (DFS) | Same problems, plus when you need post-order processing | DFS with post-order collection. More concise but less intuitive for scheduling. |
| Graph BFS/DFS | Explore all reachable nodes, find paths, connected components | General traversal — no ordering constraint. Works on cyclic graphs too. |
| Union-Find | Group connected components, detect cycles in undirected graphs | Works on undirected graphs. Doesn't produce an ordering. |
| Shortest Path (Dijkstra) | Weighted shortest path in graphs with non-negative weights | Uses priority queue, not in-degree. Works on cyclic graphs. |
Kahn's (BFS) vs DFS — When to Use Which
| Approach | Best For | Advantage |
|---|---|---|
| Kahn's (BFS) | Course schedule, parallel scheduling, 'minimum semesters' | Naturally processes in layers — nodes with same 'depth' are in the same BFS level. Easy to count levels for parallel scheduling. |
| DFS Post-Order | Alien dictionary, evaluation order, when you need reverse order | More concise code. Natural for problems where you process children before parents. Cycle detection via 'in-progress' state. |
The quick test
Ask yourself: "Is there a directed dependency relationship where A must come before B?" If yes, model it as a directed graph and apply topological sort. If the problem also asks "is it possible?" — that's cycle detection, which topological sort gives you for free.
Brute Force → Optimized Thinking
Let's walk through the thinking evolution using a concrete example: Course Schedule — given n courses and a list of prerequisites [a, b] meaning "to take course a, you must first take course b," determine if you can finish all courses.
Brute Force — Try All Permutations
Generate all possible orderings of n courses. For each ordering, check if every prerequisite is satisfied (the required course appears before the dependent one). If any valid ordering exists, return true.
function canFinish(numCourses: number, prerequisites: number[][]): boolean { // Generate all permutations of [0, 1, ..., n-1] // For each permutation, check all prerequisites // This is O(n!) permutations × O(m) checks each = impossibly slow // For n = 20 courses, that's 20! ≈ 2.4 × 10^18 permutations. // We need a fundamentally different approach. return false; // placeholder } // Problem: We're not using the structure of the dependency graph. // We don't need to try ALL orderings — we just need ONE valid one // (or determine that none exists because of a cycle).
Key Observation — Think in Terms of In-Degrees
A course with no prerequisites (in-degree 0) can be taken immediately. After taking it, it 'unlocks' courses that depended on it — their in-degree decreases by 1. When a course's in-degree hits 0, it can be taken next. This is a BFS process.
Prerequisites: [[1,0], [2,0], [3,1], [3,2]] Meaning: 0→1, 0→2, 1→3, 2→3 In-degrees: course 0 = 0, course 1 = 1, course 2 = 1, course 3 = 2 Step 1: Course 0 has in-degree 0 → take it. Reduce neighbors: course 1 → in-degree 0, course 2 → in-degree 0 Step 2: Courses 1 and 2 both have in-degree 0 → take them. Reduce neighbors: course 3 → in-degree 0 Step 3: Course 3 has in-degree 0 → take it. All 4 courses taken → possible! If there's a cycle (e.g., 1→2, 2→1), neither ever reaches in-degree 0. They're stuck. We detect this: processed count < numCourses → cycle.
Optimal — Kahn's Algorithm (BFS Topological Sort)
Build the adjacency list and compute in-degrees. Start BFS with all in-degree-0 nodes. For each processed node, reduce neighbors' in-degrees. When a neighbor hits 0, add it to the queue. If all nodes are processed, no cycle exists.
function canFinish( numCourses: number, prerequisites: number[][] ): boolean { // Build adjacency list and in-degree array const graph: number[][] = Array.from({ length: numCourses }, () => []); const inDegree = new Array(numCourses).fill(0); for (const [course, prereq] of prerequisites) { graph[prereq].push(course); // prereq → course inDegree[course]++; } // Start with all courses that have no prerequisites const queue: number[] = []; for (let i = 0; i < numCourses; i++) { if (inDegree[i] === 0) queue.push(i); } let processed = 0; while (queue.length) { const node = queue.shift()!; processed++; for (const neighbor of graph[node]) { inDegree[neighbor]--; if (inDegree[neighbor] === 0) { queue.push(neighbor); // all prerequisites met } } } return processed === numCourses; // false if cycle exists } // O(V + E): each node processed once, each edge examined once. // If processed < numCourses, some nodes are stuck in a cycle.
Why Does the Optimization Work?
From Permutations to Graph Structure
Instead of trying all orderings, we exploit the graph structure. A node can be processed only when all its incoming edges are 'resolved' (in-degree = 0). This is a local condition we can check efficiently.
Why BFS Works
BFS naturally processes nodes in 'waves' — first all nodes with no dependencies, then nodes whose dependencies are all in the first wave, and so on. Each wave represents tasks that can be done in parallel.
Cycle Detection for Free
If a cycle exists, the nodes in the cycle never reach in-degree 0 — they're waiting for each other forever. So processed < numCourses. We don't need a separate cycle detection algorithm.
The thinking process matters more than the code
In interviews, explain: "I'll model this as a directed graph where an edge from A to B means A is a prerequisite for B. Then I'll use Kahn's algorithm: start with nodes that have no prerequisites (in-degree 0), process them, and unlock their dependents. If I can process all nodes, there's no cycle and a valid ordering exists."
Core Templates
There are two core templates for topological sort. Both produce valid orderings and detect cycles. Choose based on the problem's needs.
Template 1: Kahn's Algorithm (BFS / In-Degree)
The most intuitive approach. Process nodes in dependency order using a queue. Naturally gives you the topological order and detects cycles. Best for scheduling problems where you need level-by-level processing.
function topoSortKahn( numNodes: number, edges: [number, number][] // [from, to] = from must come before to ): number[] { // 1. Build adjacency list and in-degree array const graph: number[][] = Array.from({ length: numNodes }, () => []); const inDegree = new Array(numNodes).fill(0); for (const [from, to] of edges) { graph[from].push(to); inDegree[to]++; } // 2. Initialize queue with all in-degree 0 nodes const queue: number[] = []; for (let i = 0; i < numNodes; i++) { if (inDegree[i] === 0) queue.push(i); } // 3. BFS: process nodes, reduce neighbors' in-degrees const order: number[] = []; while (queue.length) { const node = queue.shift()!; order.push(node); for (const neighbor of graph[node]) { inDegree[neighbor]--; if (inDegree[neighbor] === 0) { queue.push(neighbor); } } } // 4. Cycle detection: if not all nodes processed, cycle exists if (order.length !== numNodes) { return []; // cycle detected — no valid ordering } return order; } // Time: O(V + E) — each node and edge processed once // Space: O(V + E) — adjacency list + in-degree array + queue
Template 2: DFS Post-Order
Run DFS from each unvisited node. After processing all descendants, add the current node to a stack. The reversed stack is the topological order. Cycle detection uses a three-state coloring: white (unvisited), gray (in progress), black (done).
function topoSortDFS( numNodes: number, edges: [number, number][] ): number[] { const graph: number[][] = Array.from({ length: numNodes }, () => []); for (const [from, to] of edges) { graph[from].push(to); } // 0 = unvisited, 1 = in progress (gray), 2 = done (black) const state = new Array(numNodes).fill(0); const order: number[] = []; let hasCycle = false; function dfs(node: number): void { if (hasCycle) return; if (state[node] === 1) { hasCycle = true; return; } // cycle! if (state[node] === 2) return; // already processed state[node] = 1; // mark in progress for (const neighbor of graph[node]) { dfs(neighbor); } state[node] = 2; // mark done order.push(node); // post-order: add AFTER all descendants } for (let i = 0; i < numNodes; i++) { if (state[i] === 0) dfs(i); } if (hasCycle) return []; return order.reverse(); // reverse post-order = topological order } // Why reverse? In DFS post-order, a node is added AFTER its descendants. // So descendants appear before the node in the array. // Reversing gives us: node before descendants = topological order.
Quick Reference: Kahn's vs DFS
| Aspect | Kahn's (BFS) | DFS Post-Order |
|---|---|---|
| Approach | BFS with in-degree queue | DFS with post-order collection |
| Order produced | Direct topological order | Reverse post-order (need to reverse) |
| Cycle detection | processed < numNodes | Encountering a 'gray' (in-progress) node |
| Level-by-level | Natural — each BFS level is a parallel batch | Not natural — need extra work |
| Code complexity | Slightly more setup (in-degree array) | More concise, but 3-state coloring |
| Best for | Scheduling, parallel execution, course order | Alien dictionary, evaluation order |
Which template to pick?
Default to Kahn's — it's more intuitive and handles most problems. Use DFS when: (1) you need post-order processing (process children before parent), (2) the problem naturally fits recursive exploration (like alien dictionary), or (3) you need to detect cycles with the three-state coloring for a specific reason.
Variations & Sub-Patterns
The two templates cover the basics, but real interview problems add twists. Here are the most important variations.
| Variation | Twist on the Template | Classic Example |
|---|---|---|
| Can finish? (cycle detection) | Just check if processed === numNodes. Don't need the actual order | Course Schedule (LC 207) |
| Find the order | Return the topological order array instead of just true/false | Course Schedule II (LC 210) |
| Parallel scheduling (min time) | Kahn's with level counting — each BFS level = one time unit | Parallel Courses (LC 1136) |
| Alien dictionary | Derive edges from comparing adjacent words, then topo sort the letters | Alien Dictionary (LC 269) |
| Longest path in DAG | Topo sort first, then DP: dist[v] = max(dist[u] + 1) for all u → v | Longest Increasing Path in Matrix (LC 329) |
| Lexicographically smallest order | Use a min-heap instead of a regular queue in Kahn's | Sequence Reconstruction (LC 444) |
| Task scheduling with cooldown | Combine topo sort with greedy scheduling and cooldown constraints | Task Scheduler (LC 621) |
Deep Dive: Alien Dictionary
This is the hardest and most creative topological sort problem. You're given a list of words sorted in an alien language. Derive the ordering of letters. The trick: compare adjacent words character by character. The first difference gives you an edge: letter A comes before letter B.
Problems mentioned above
function alienOrder(words: string[]): string { // 1. Initialize graph for all characters that appear const graph = new Map<string, Set<string>>(); const inDegree = new Map<string, number>(); for (const word of words) { for (const ch of word) { if (!graph.has(ch)) graph.set(ch, new Set()); if (!inDegree.has(ch)) inDegree.set(ch, 0); } } // 2. Derive edges by comparing adjacent words for (let i = 0; i < words.length - 1; i++) { const w1 = words[i], w2 = words[i + 1]; // Edge case: "abc" before "ab" is invalid (longer word can't come first) if (w1.length > w2.length && w1.startsWith(w2)) return ""; for (let j = 0; j < Math.min(w1.length, w2.length); j++) { if (w1[j] !== w2[j]) { // First difference: w1[j] comes before w2[j] if (!graph.get(w1[j])!.has(w2[j])) { graph.get(w1[j])!.add(w2[j]); inDegree.set(w2[j], (inDegree.get(w2[j]) ?? 0) + 1); } break; // only the FIRST difference matters } } } // 3. Kahn's algorithm const queue: string[] = []; for (const [ch, deg] of inDegree) { if (deg === 0) queue.push(ch); } const order: string[] = []; while (queue.length) { const ch = queue.shift()!; order.push(ch); for (const neighbor of graph.get(ch)!) { inDegree.set(neighbor, inDegree.get(neighbor)! - 1); if (inDegree.get(neighbor) === 0) queue.push(neighbor); } } // Cycle check if (order.length !== inDegree.size) return ""; return order.join(""); } // Key insight: only the FIRST differing character between adjacent // words gives us an edge. Characters after that tell us nothing.
Deep Dive: Parallel Courses (Minimum Semesters)
This is Kahn's algorithm with level counting. Each BFS level represents one semester — all courses in the same level can be taken in parallel. The number of levels = minimum semesters needed.
function minimumSemesters(n: number, relations: number[][]): number { const graph: number[][] = Array.from({ length: n + 1 }, () => []); const inDegree = new Array(n + 1).fill(0); for (const [prev, next] of relations) { graph[prev].push(next); inDegree[next]++; } const queue: number[] = []; for (let i = 1; i <= n; i++) { if (inDegree[i] === 0) queue.push(i); } let semesters = 0; let processed = 0; while (queue.length) { semesters++; const levelSize = queue.length; // courses this semester for (let i = 0; i < levelSize; i++) { const course = queue.shift()!; processed++; for (const next of graph[course]) { inDegree[next]--; if (inDegree[next] === 0) queue.push(next); } } } return processed === n ? semesters : -1; // -1 if cycle } // Same as regular Kahn's, but we count BFS levels. // Each level = one semester of parallel courses.
Lexicographic order
If the problem asks for the lexicographically smallest topological order, replace the regular queue in Kahn's with a min-heap (priority queue). This ensures that among all valid next nodes, you always pick the smallest one first.
Visual Walkthrough
Let's trace through both algorithms on the same graph to see how they produce a topological ordering.
Kahn's Algorithm — Step by Step
Graph (6 nodes, edges represent "must come before"): 5 → 0, 5 → 2 4 → 0, 4 → 1 2 → 3 3 → 1 Adjacency list: 0: [] 1: [] 2: [3] 3: [1] 4: [0, 1] 5: [0, 2] In-degrees: 0: 2 (from 5, 4) 1: 2 (from 4, 3) 2: 1 (from 5) 3: 1 (from 2) 4: 0 ← start here 5: 0 ← start here Step 1: Queue = [4, 5], order = [] Process 4: reduce in-degree of 0 (→1), 1 (→1) Process 5: reduce in-degree of 0 (→0 ✓), 2 (→0 ✓) Queue = [0, 2], order = [4, 5] Step 2: Queue = [0, 2] Process 0: no neighbors Process 2: reduce in-degree of 3 (→0 ✓) Queue = [3], order = [4, 5, 0, 2] Step 3: Queue = [3] Process 3: reduce in-degree of 1 (→0 ✓) Queue = [1], order = [4, 5, 0, 2, 3] Step 4: Queue = [1] Process 1: no neighbors Queue = [], order = [4, 5, 0, 2, 3, 1] All 6 nodes processed → valid topological order: [4, 5, 0, 2, 3, 1] Note: [5, 4, 2, 0, 3, 1] is also valid — topo sort isn't unique.
DFS Post-Order — Step by Step
Same graph: 5→0, 5→2, 4→0, 4→1, 2→3, 3→1 DFS from node 0 (unvisited): 0 has no neighbors → post-order: add 0 Stack: [0] DFS from node 1 (unvisited): 1 has no neighbors → post-order: add 1 Stack: [0, 1] DFS from node 2 (unvisited): Visit 3 → visit 1 (already done, skip) → add 3 Back to 2 → add 2 Stack: [0, 1, 3, 2] DFS from node 3 (already done, skip) DFS from node 4 (unvisited): Visit 0 (done), visit 1 (done) → add 4 Stack: [0, 1, 3, 2, 4] DFS from node 5 (unvisited): Visit 0 (done), visit 2 (done) → add 5 Stack: [0, 1, 3, 2, 4, 5] Reverse stack: [5, 4, 2, 3, 1, 0] Valid topological order: [5, 4, 2, 3, 1, 0] ✓ (Different from Kahn's result, but both are valid.)
Cycle Detection — What Happens with a Cycle
Graph with cycle: 0→1, 1→2, 2→0 (cycle: 0→1→2→0) ═══ Kahn's Algorithm ═══ In-degrees: 0:1, 1:1, 2:1 No node has in-degree 0 → queue starts empty! Processed = 0, numNodes = 3 → 0 ≠ 3 → CYCLE DETECTED ═══ DFS Three-State ═══ DFS from 0: state[0] = GRAY (in progress) Visit 1: state[1] = GRAY Visit 2: state[2] = GRAY Visit 0: state[0] = GRAY → CYCLE DETECTED! (We found a node that's still "in progress" — we're in a loop) Both methods detect the cycle. Kahn's detects it passively (not all nodes processed). DFS detects it actively (gray node revisited).
Practice Problems
These 7 problems are carefully ordered from easy to hard. Each one teaches a different aspect of topological sort. Solve them in order.
LC 207. Course Schedule
Why this pattern:
The purest topological sort problem: can you finish all courses? This is just cycle detection on a directed graph. If a valid topological order exists, return true.
Key idea: Build a directed graph from prerequisites. Run Kahn's algorithm. If processed === numCourses, no cycle exists → return true. This is the foundation — every other topo sort problem builds on this.
LC 210. Course Schedule II
Why this pattern:
Same as Course Schedule, but return the actual ordering instead of just true/false. Collect the order during Kahn's BFS and return it.
Key idea: Identical to Course Schedule, but push each processed node to an order array. Return the array if all nodes are processed, empty array if cycle detected. The order array IS the topological sort.
LC 1971. Find if Path Exists in Graph
Why this pattern:
While not strictly a topo sort problem, it builds graph traversal fundamentals. Given an undirected graph, determine if a path exists between two nodes. Good warm-up for graph modeling.
Key idea: Build an adjacency list. BFS or DFS from source. If you reach destination, return true. This problem teaches graph construction — the same skill needed for topo sort problems.
LC 1136. Parallel Courses
Why this pattern:
Kahn's algorithm with level counting. Each BFS level represents one semester of courses that can be taken in parallel. The number of levels = minimum semesters.
Key idea: Standard Kahn's, but track BFS levels: capture queue.length before the inner loop (same trick as tree level-order). Each level = one semester. Count levels. Return -1 if cycle.
LC 1462. Course Schedule IV
Why this pattern:
Topological sort + reachability. For each query [u, v], determine if u is a prerequisite of v (directly or transitively). Process in topo order, propagating reachability sets.
Key idea: Topo sort the courses. For each course in topo order, its reachable set = union of all direct prerequisites' reachable sets + direct prerequisites themselves. Use bitsets or sets for efficiency. Answer queries from the reachability data.
LC 269. Alien Dictionary
Why this pattern:
The creative topo sort problem. Derive ordering edges by comparing adjacent words (first differing character gives an edge). Then run Kahn's on the letter graph. Handle edge cases: invalid input, disconnected letters.
Key idea: Compare adjacent words character by character. First difference w1[j] ≠ w2[j] → edge from w1[j] to w2[j]. Only the FIRST difference matters. Edge case: if w1 is longer than w2 and w1 starts with w2, the input is invalid.
LC 329. Longest Increasing Path in a Matrix
Why this pattern:
Topological sort on an implicit DAG. Each cell has edges to neighbors with smaller values. Process cells in topological order (smallest values first) and compute longest path using DP.
Key idea: Model the matrix as a DAG: cell (i,j) → (r,c) if matrix[i][j] < matrix[r][c]. Compute in-degrees. Kahn's BFS from cells with in-degree 0 (local minima). Track distance at each cell. Max distance = answer. Alternatively, DFS + memoization.
Practice strategy
For each problem: (1) Identify the nodes and edges (what depends on what?). (2) Choose Kahn's or DFS. (3) Handle the cycle case. (4) After solving, write: "Nodes are [X], edges mean [Y], I used [Kahn's/DFS] because [reason]."
Common Mistakes
These are the traps that catch people on topological sort problems. Learn them here so you don't learn them in an interview.
Reversing the edge direction
The problem says 'to take course A, you need course B' and you create an edge A → B instead of B → A. This reverses the entire dependency order.
✅Read carefully: 'B is a prerequisite for A' means B → A (B must come before A). Draw a small example: 'to take Algorithms, you need Data Structures' → DS → Algo. The prerequisite points TO the dependent course.
Forgetting to check for cycles
You run Kahn's algorithm and return the order, but don't check if all nodes were processed. If there's a cycle, some nodes are silently missing from the result.
✅ALWAYS check: if (order.length !== numNodes) return [] or return -1. This is the cycle detection step. Without it, you return a partial (invalid) ordering for graphs with cycles.
Not initializing all nodes in the graph
You only add nodes that appear in edges, missing isolated nodes (nodes with no dependencies and no dependents). These nodes have in-degree 0 and should be in the result.
✅Initialize the graph and in-degree array for ALL nodes (0 to numNodes-1), not just nodes that appear in edges. Isolated nodes are valid — they can go anywhere in the order.
Using two states instead of three in DFS cycle detection
You use visited/unvisited (two states) for DFS cycle detection. This doesn't work for directed graphs — you can't distinguish between a back edge (cycle) and a cross edge (already processed in a different branch).
✅Use THREE states: unvisited (0), in-progress/gray (1), done/black (2). A cycle exists only when you encounter a GRAY node (still on the current DFS path). A BLACK node is fine — it was processed in a different branch.
Alien Dictionary: not handling edge cases
You miss the case where a longer word comes before its prefix (e.g., 'abc' before 'ab'). This is an invalid ordering in any language. You also might miss characters that appear in words but have no ordering constraints.
✅Check: if w1 is longer than w2 and w1 starts with w2, return '' (invalid). Also: initialize ALL characters that appear in any word, even if they have no edges. They should still appear in the output.
Using DFS without post-order for topological sort
You add nodes to the result when you ENTER them (pre-order) instead of when you LEAVE them (post-order). This gives the wrong order — a node might appear before its dependencies.
✅In DFS topo sort, add the node to the stack AFTER visiting all its neighbors (post-order). Then reverse the stack. Pre-order DFS does NOT give topological order.
Interview Strategy
Topological sort problems are a FAANG favorite because they test graph modeling, algorithm choice, and edge case handling. Here's how to approach them.
The 5-Step Interview Flow
Identify the Graph
'The nodes are courses and the edges are prerequisites. An edge from B to A means B must come before A.' — Explicitly state what the nodes and edges represent. Draw a small example graph.
Check for DAG Requirement
'This is a directed graph. If there's a cycle, no valid ordering exists, so I need to detect cycles. I'll use Kahn's algorithm which gives me both the ordering and cycle detection.' — Mention the DAG requirement.
Choose the Algorithm
'I'll use Kahn's algorithm (BFS with in-degrees) because it naturally processes nodes in dependency order and detects cycles when not all nodes are processed.' — Or explain why DFS is better for this specific problem.
Code It Clean
Build adjacency list + in-degree array. Initialize queue with in-degree 0 nodes. BFS loop: process, reduce neighbors, enqueue when in-degree hits 0. Check processed count. Use clear variable names.
Test Edge Cases
Trace through: (1) no edges (all nodes independent), (2) linear chain (A→B→C), (3) cycle (return false/empty), (4) disconnected components, (5) single node. State complexity: O(V + E) time and space.
Common Follow-Up Questions
| Follow-Up | How to Handle |
|---|---|
| "Is the topological order unique?" | It's unique only if the queue never has more than one element at a time (i.e., there's exactly one valid ordering). If the queue has multiple elements, there are multiple valid orderings. |
| "What if I need the lexicographically smallest order?" | Replace the queue with a min-heap (priority queue) in Kahn's algorithm. This ensures you always process the smallest available node first. |
| "Can you do it with DFS instead?" | Yes — DFS post-order with three-state coloring. Add nodes to stack after visiting all descendants, then reverse. Cycle = encountering a gray node. Show you know both approaches. |
| "What's the minimum time to complete all tasks in parallel?" | Use Kahn's with level counting. Each BFS level = one time unit (all tasks in the same level can run in parallel). Number of levels = minimum time. This is the critical path. |
| "What if edges are weighted (tasks have different durations)?" | This becomes the critical path method. Topo sort first, then DP: earliest_start[v] = max(earliest_start[u] + duration[u]) for all u → v. The maximum earliest_start + duration = total time. |
| "What if the graph is not a DAG?" | Topological sort only works on DAGs. For general directed graphs, you'd need to find strongly connected components (Tarjan's or Kosaraju's) first, then topo sort the condensed DAG. |
The meta-skill
The hardest part of topological sort problems isn't the algorithm — it's modeling the problem as a graph. "What are the nodes? What are the edges? Which direction do the edges go?" Once you have the graph, Kahn's algorithm is mechanical. Practice the modeling step: read a problem, draw the graph, verify the edge directions with a small example.
Summary & Cheat Sheet
Pin this section. Review it before mock interviews and contests.
Quick Revision Cheat Sheet
Core idea: Order nodes in a DAG so every edge goes left → right. If cycle exists, no valid order.
Kahn's (BFS): Compute in-degrees → queue all in-degree 0 → process, reduce neighbors → enqueue when 0. O(V+E)
DFS post-order: DFS with 3 states (white/gray/black). Add to stack AFTER all descendants. Reverse = topo order
Cycle detection: Kahn's: processed < numNodes. DFS: encounter a gray (in-progress) node
Edge direction: 'B is prerequisite for A' → edge B → A. The prerequisite POINTS TO the dependent
Parallel scheduling: Kahn's with level counting. Each BFS level = one time unit. Levels = min parallel time
Lex smallest order: Replace Kahn's queue with a min-heap. Always pick the smallest available node
Alien dictionary: Compare adjacent words → first diff char gives edge. Then Kahn's on letter graph
Longest path in DAG: Topo sort first, then DP: dist[v] = max(dist[u] + weight) for all u → v
Three states (DFS): 0=unvisited, 1=in-progress (gray), 2=done (black). Gray→gray = cycle. Two states is NOT enough
Complexity: Time: O(V + E). Space: O(V + E). Both approaches are optimal
Mental trigger: "Prerequisites" + "ordering" + "can it be done?" → model as directed graph → topological sort
Decision Flowchart
Is there a dependency/ordering relationship? ├── YES → Model as directed graph (prerequisite → dependent) │ ├── "Can all tasks be completed?" (cycle detection) │ │ → Kahn's: check processed === numNodes │ ├── "Find a valid ordering" │ │ → Kahn's: return the order array │ ├── "Minimum time with parallel execution" │ │ → Kahn's with level counting (BFS levels = time units) │ ├── "Lexicographically smallest ordering" │ │ → Kahn's with min-heap instead of queue │ ├── "Derive ordering from comparisons" (alien dictionary) │ │ → Extract edges from adjacent comparisons → Kahn's │ ├── "Longest path in DAG" │ │ → Topo sort → DP forward pass │ └── "Is the ordering unique?" │ → Kahn's: unique iff queue never has > 1 element └── NO → Not a topological sort problem → Consider: BFS/DFS traversal, Union-Find, shortest path
One last thing
Topological sort is one of the most elegant algorithms in CS. It solves a real-world problem (dependency ordering) with a simple, efficient approach. The algorithm itself is straightforward — the challenge is recognizing when to use it and modeling the problem correctly. Solve the 7 problems in Section 07, focus on getting the edge directions right, and you'll handle any dependency-ordering question with confidence.