Topological SortDAGKahn's AlgorithmDFS Post-OrderDependenciesOrdering

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.

40 min read10 sections
01

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

Two Ways to Topological Sorttext
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 processedvalid order. If notcycle 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.

02

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

PatternWhen to UseKey Difference
Topological Sort (Kahn's)Order tasks with dependencies, detect cycles, parallel schedulingBFS 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 processingDFS with post-order collection. More concise but less intuitive for scheduling.
Graph BFS/DFSExplore all reachable nodes, find paths, connected componentsGeneral traversal — no ordering constraint. Works on cyclic graphs too.
Union-FindGroup connected components, detect cycles in undirected graphsWorks on undirected graphs. Doesn't produce an ordering.
Shortest Path (Dijkstra)Weighted shortest path in graphs with non-negative weightsUses priority queue, not in-degree. Works on cyclic graphs.

Kahn's (BFS) vs DFS — When to Use Which

ApproachBest ForAdvantage
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-OrderAlien dictionary, evaluation order, when you need reverse orderMore 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.

03

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.

1

Brute Force — Try All Permutations

O(n! · m) time

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.

Brute Forcetypescript
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).
2

Key Observation — Think in Terms of In-Degrees

Thinking step

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.

The Insighttext
Prerequisites: [[1,0], [2,0], [3,1], [3,2]]
Meaning: 01, 02, 13, 23

In-degrees: course 0 = 0, course 1 = 1, course 2 = 1, course 3 = 2

Step 1: Course 0 has in-degree 0take it.
        Reduce neighbors: course 1in-degree 0, course 2in-degree 0

Step 2: Courses 1 and 2 both have in-degree 0take them.
        Reduce neighbors: course 3in-degree 0

Step 3: Course 3 has in-degree 0take it.

All 4 courses takenpossible!

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.
3

Optimal — Kahn's Algorithm (BFS Topological Sort)

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

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.

Kahn's Algorithm — Optimaltypescript
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?

1

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.

2

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.

3

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."

04

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.

Kahn's Algorithm Templatetypescript
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).

DFS Post-Order Templatetypescript
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

AspectKahn's (BFS)DFS Post-Order
ApproachBFS with in-degree queueDFS with post-order collection
Order producedDirect topological orderReverse post-order (need to reverse)
Cycle detectionprocessed < numNodesEncountering a 'gray' (in-progress) node
Level-by-levelNatural — each BFS level is a parallel batchNot natural — need extra work
Code complexitySlightly more setup (in-degree array)More concise, but 3-state coloring
Best forScheduling, parallel execution, course orderAlien 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.

05

Variations & Sub-Patterns

The two templates cover the basics, but real interview problems add twists. Here are the most important variations.

VariationTwist on the TemplateClassic Example
Can finish? (cycle detection)Just check if processed === numNodes. Don't need the actual orderCourse Schedule (LC 207)
Find the orderReturn the topological order array instead of just true/falseCourse Schedule II (LC 210)
Parallel scheduling (min time)Kahn's with level counting — each BFS level = one time unitParallel Courses (LC 1136)
Alien dictionaryDerive edges from comparing adjacent words, then topo sort the lettersAlien Dictionary (LC 269)
Longest path in DAGTopo sort first, then DP: dist[v] = max(dist[u] + 1) for all u → vLongest Increasing Path in Matrix (LC 329)
Lexicographically smallest orderUse a min-heap instead of a regular queue in Kahn'sSequence Reconstruction (LC 444)
Task scheduling with cooldownCombine topo sort with greedy scheduling and cooldown constraintsTask 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

Alien Dictionary — Edge Derivation + Topo Sorttypescript
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.

Parallel Courses — Kahn's with Level Countingtypescript
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.

06

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

Kahn's Algorithm — Tracetext
Graph (6 nodes, edges represent "must come before"):
  50, 52
  40, 41
  23
  31

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: 0start here
  5: 0start 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 processedvalid topological order: [4, 5, 0, 2, 3, 1]

Note: [5, 4, 2, 0, 3, 1] is also validtopo sort isn't unique.

DFS Post-Order — Step by Step

DFS Post-Order — Tracetext
Same graph: 50, 52, 40, 41, 23, 31

DFS from node 0 (unvisited):
  0 has no neighborspost-order: add 0
  Stack: [0]

DFS from node 1 (unvisited):
  1 has no neighborspost-order: add 1
  Stack: [0, 1]

DFS from node 2 (unvisited):
  Visit 3visit 1 (already done, skip) → add 3
  Back to 2add 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

Cycle Detection — Tracetext
Graph with cycle: 01, 12, 20 (cycle: 0120)

═══ 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).
07

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.

1

LC 207. Course Schedule

Medium

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.

2

LC 210. Course Schedule II

Medium

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.

3

LC 1971. Find if Path Exists in Graph

Easy

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.

4

LC 1136. Parallel Courses

Medium

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.

5

LC 1462. Course Schedule IV

Medium

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.

6

LC 269. Alien Dictionary

Hard

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.

7

LC 329. Longest Increasing Path in a Matrix

Hard

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]."

08

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.

09

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

1

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.

2

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.

3

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.

4

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.

5

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-UpHow 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.

10

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

Topological Sort — Decision Treetext
Is there a dependency/ordering relationship?
├── YESModel as directed graph (prerequisitedependent)
│   ├── "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 comparisonsKahn's
│   ├── "Longest path in DAG"
│   │   → Topo sortDP forward pass
│   └── "Is the ordering unique?"
│       → Kahn's: unique iff queue never has > 1 element
└── NONot 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.