DSA Tutorial
🔍

Topological Sort

What Is Topological Sort?

A topological sort of a directed graph is a linear ordering of its vertices such that for every directed edge u → v, vertex u appears before vertex v in the ordering.

DAG:
     5 ──→ 0 ←── 4
     │             │
     └──→ 2 ──→ 3 ─┘
               │
               └──→ 1

Valid topological orderings (multiple exist):
  [5, 4, 2, 3, 1, 0]  ← one valid ordering
  [4, 5, 2, 3, 1, 0]  ← another valid ordering
  [5, 4, 0, 2, 3, 1]  ← another valid ordering

RULE: for every edge u→v, u must appear BEFORE v.

Verify [5, 4, 2, 3, 1, 0]:
  5→0: 5 is at index 0, 0 is at index 5. 0 < 5 ✓
  5→2: 5 is at index 0, 2 is at index 2. 0 < 2 ✓
  4→0: 4 is at index 1, 0 is at index 5. 1 < 5 ✓
  4→3: 4 is at index 1, 3 is at index 3. 1 < 3 ✓
  2→3: 2 is at index 2, 3 is at index 3. 2 < 3 ✓
  3→1: 3 is at index 3, 1 is at index 4. 3 < 4 ✓
  All edges satisfied ✓

Real-World Applications

COURSE SCHEDULING:
  Courses as vertices. Edge A→B means "take A before B".
  Topological sort gives a valid study order.

     Math101 ──→ Math201 ──→ Math301
     Math101 ──→ CS101   ──→ CS201
                 CS101   ──→ CS301

  Valid order: Math101, CS101, Math201, CS101, CS201, CS301, Math301

BUILD SYSTEMS (Make, Gradle, CMake):
  Files as vertices. Edge A→B means "compile A before B".
  Topological sort gives valid compilation order.

TASK SCHEDULING:
  Tasks as vertices. Edge A→B means "A must finish before B starts".
  Any topological order is a valid execution plan.

PACKAGE MANAGERS (npm, pip, apt):
  Packages as vertices. Edge A→B means "install A before B".
  Topological sort gives valid installation order.

Approach 1: DFS Post-Order Topological Sort

Use DFS. When a vertex finishes (all its descendants explored), push it onto a stack. Pop the stack at the end for topological order.

WHY POST-ORDER WORKS:
  For edge u→v: DFS explores v (and all its descendants) BEFORE
  finishing u. So v is pushed to stack BEFORE u.
  When we pop the stack, u comes out BEFORE v.
  u → v means u before v ✓

ALGORITHM:
  visited = [false] * V
  stack   = []

  for each unvisited vertex (outer loop):
    dfs(vertex, visited, stack)

  dfs(u, visited, stack):
    visited[u] = true
    for each neighbour v of u:
      if not visited[v]: dfs(v)
    stack.push(u)   ← push AFTER all descendants finished (post-order)

  topological order = stack popped top-to-bottom (or reversed)
Java
1import java.util.*; 2 3public class TopologicalSortDFS { 4 5 // DFS helper — pushes to stack after all descendants are done 6 public static void dfs(int node, 7 ArrayList<ArrayList<Integer>> graph, 8 boolean[] visited, 9 Stack<Integer> stack) { 10 visited[node] = true; 11 12 for (int nbr : graph.get(node)) { 13 if (!visited[nbr]) { 14 dfs(nbr, graph, visited, stack); 15 } 16 } 17 18 stack.push(node); // Push AFTER all descendants (post-order) 19 } 20 21 public static List<Integer> topoSort(int V, 22 ArrayList<ArrayList<Integer>> graph) { 23 boolean[] visited = new boolean[V]; 24 Stack<Integer> stack = new Stack<>(); 25 26 // Outer loop handles disconnected components 27 for (int i = 0; i < V; i++) { 28 if (!visited[i]) { 29 dfs(i, graph, visited, stack); 30 } 31 } 32 33 // Pop stack → topological order 34 List<Integer> result = new ArrayList<>(); 35 while (!stack.isEmpty()) { 36 result.add(stack.pop()); 37 } 38 return result; 39 } 40 41 public static void main(String[] args) { 42 Scanner sc = new Scanner(System.in); 43 int V = sc.nextInt(); 44 int E = sc.nextInt(); 45 46 ArrayList<ArrayList<Integer>> graph = new ArrayList<>(); 47 for (int i = 0; i < V; i++) graph.add(new ArrayList<>()); 48 49 for (int i = 0; i < E; i++) { 50 int u = sc.nextInt(); 51 int v = sc.nextInt(); 52 graph.get(u).add(v); // Directed edge u → v 53 } 54 55 List<Integer> order = topoSort(V, graph); 56 System.out.println("Topological Order (DFS): " + order); 57 } 58} 59 60/* 61Sample Input: 626 6 635 0 645 2 654 0 664 1 672 3 683 1 69 70Sample Output: 71Topological Order (DFS): [5, 4, 2, 3, 1, 0] 72 (or another valid ordering) 73*/
Input:
6 6
5 0
5 2
4 0
4 1
2 3
3 1

Output:
Topological Order (DFS): [5, 4, 2, 3, 1, 0]

Dry Run: DFS Topological Sort

DAG:  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]

visited=[F,F,F,F,F,F], stack=[]

Outer loop i=0: not visited
  dfs(0): visited[0]=T, no neighbours → stack.push(0) → stack=[0]

Outer loop i=1: not visited
  dfs(1): visited[1]=T, no neighbours → stack.push(1) → stack=[0,1]

Outer loop i=2: not visited
  dfs(2): visited[2]=T
    nbr=3: not visited
      dfs(3): visited[3]=T
        nbr=1: visited → skip
        stack.push(3) → stack=[0,1,3]
    stack.push(2) → stack=[0,1,3,2]

Outer loop i=3: already visited → skip
Outer loop i=4: not visited
  dfs(4): visited[4]=T
    nbr=0: visited → skip
    nbr=1: visited → skip
    stack.push(4) → stack=[0,1,3,2,4]

Outer loop i=5: not visited
  dfs(5): visited[5]=T
    nbr=0: visited → skip
    nbr=2: visited → skip
    stack.push(5) → stack=[0,1,3,2,4,5]

Pop stack → [5, 4, 2, 3, 1, 0] ✓

Verify: every edge u→v has u before v:
  5→0: 5 at 0, 0 at 5 ✓
  5→2: 5 at 0, 2 at 2 ✓
  4→0: 4 at 1, 0 at 5 ✓
  4→1: 4 at 1, 1 at 4 ✓
  2→3: 2 at 2, 3 at 3 ✓
  3→1: 3 at 3, 1 at 4 ✓

Approach 2: Kahn's Algorithm (BFS / In-Degree Method)

Compute in-degrees. Start BFS with all zero-in-degree vertices. Process and remove vertices one by one, revealing new zero-in-degree vertices.

KAHN'S ALGORITHM:
  1. Compute in-degree of every vertex.
  2. Enqueue all vertices with in-degree = 0.
  3. While queue not empty:
       u = dequeue
       add u to result
       for each v in adj[u]:
         in-degree[v]--
         if in-degree[v] == 0: enqueue v
  4. If result.size == V: valid topological order.
     If result.size <  V: cycle exists (not a DAG).

INTUITION — PEELING LAYERS:
  Start with vertices that have no prerequisites (in-degree 0).
  After "doing" them, mark their outgoing edges as complete.
  New vertices become ready (in-degree drops to 0).
  Repeat — like peeling layers off an onion.

DAG: 5→0, 5→2, 4→0, 4→1, 2→3, 3→1

In-degrees:
  0:2  1:2  2:1  3:1  4:0  5:0

Queue initially: [4, 5]  (in-degree 0)

Process 4: result=[4]
  4→0: in-degree[0] = 2-1 = 1
  4→1: in-degree[1] = 2-1 = 1
  Queue: [5]

Process 5: result=[4,5]
  5→0: in-degree[0] = 1-1 = 0 → enqueue 0
  5→2: in-degree[2] = 1-1 = 0 → enqueue 2
  Queue: [0, 2]

Process 0: result=[4,5,0]
  0 has no outgoing edges.
  Queue: [2]

Process 2: result=[4,5,0,2]
  2→3: in-degree[3] = 1-1 = 0 → enqueue 3
  Queue: [3]

Process 3: result=[4,5,0,2,3]
  3→1: in-degree[1] = 1-1 = 0 → enqueue 1
  Queue: [1]

Process 1: result=[4,5,0,2,3,1]
  1 has no outgoing edges.
  Queue: []

result.size = 6 = V → No cycle! ✓
Topological order: [4, 5, 0, 2, 3, 1]
Java
1import java.util.*; 2 3public class TopologicalSortKahns { 4 5 public static List<Integer> kahnSort(int V, 6 ArrayList<ArrayList<Integer>> graph) { 7 // Step 1: Compute in-degrees 8 int[] inDegree = new int[V]; 9 for (int u = 0; u < V; u++) { 10 for (int v : graph.get(u)) { 11 inDegree[v]++; 12 } 13 } 14 15 // Step 2: Enqueue all vertices with in-degree 0 16 Queue<Integer> queue = new LinkedList<>(); 17 for (int i = 0; i < V; i++) { 18 if (inDegree[i] == 0) queue.offer(i); 19 } 20 21 // Step 3: BFS — peel vertices one by one 22 List<Integer> result = new ArrayList<>(); 23 24 while (!queue.isEmpty()) { 25 int u = queue.poll(); 26 result.add(u); 27 28 for (int v : graph.get(u)) { 29 inDegree[v]--; 30 if (inDegree[v] == 0) queue.offer(v); 31 } 32 } 33 34 // Step 4: Cycle check 35 if (result.size() != V) { 36 System.out.println("Cycle detected — topological sort impossible!"); 37 return new ArrayList<>(); 38 } 39 40 return result; 41 } 42 43 public static void main(String[] args) { 44 Scanner sc = new Scanner(System.in); 45 int V = sc.nextInt(); 46 int E = sc.nextInt(); 47 48 ArrayList<ArrayList<Integer>> graph = new ArrayList<>(); 49 for (int i = 0; i < V; i++) graph.add(new ArrayList<>()); 50 51 for (int i = 0; i < E; i++) { 52 int u = sc.nextInt(); 53 int v = sc.nextInt(); 54 graph.get(u).add(v); 55 } 56 57 List<Integer> order = kahnSort(V, graph); 58 if (!order.isEmpty()) { 59 System.out.println("Topological Order (Kahn's): " + order); 60 } 61 } 62} 63 64/* 65Sample Input (DAG): 666 6 675 0 685 2 694 0 704 1 712 3 723 1 73 74Sample Output: 75Topological Order (Kahn's): [4, 5, 0, 2, 3, 1] 76 77Sample Input (cyclic): 783 3 790 1 801 2 812 0 82 83Sample Output: 84Cycle detected — topological sort impossible! 85*/
DAG Input:
6 6
5 0  5 2  4 0  4 1  2 3  3 1

Output:
Topological Order (Kahn's): [4, 5, 0, 2, 3, 1]

Cyclic Input:
3 3
0 1  1 2  2 0

Output:
Cycle detected — topological sort impossible!

Application: Course Schedule

Classic LeetCode problem — can you finish all courses given prerequisites?

Problem:
  n courses labeled 0 to n-1.
  prerequisites[i] = [a, b] means you must take b before a.
  Can you finish all courses?

Model as directed graph:
  Edge b → a (b must come before a)

Example:
  n=4, prerequisites = [[1,0],[2,0],[3,1],[3,2]]
  Edges: 0→1, 0→2, 1→3, 2→3

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

  Kahn's:
    Queue=[0]
    Process 0: result=[0], reduce 1→0, 2→0 → queue=[1,2]
    Process 1: result=[0,1], reduce 3→1 → 3's in-degree=1
    Process 2: result=[0,1,2], reduce 3→0 → 3's in-degree=0 → queue=[3]
    Process 3: result=[0,1,2,3]
    result.size(4) == n(4) → CAN finish! ✓
    Valid order: [0, 1, 2, 3] or [0, 2, 1, 3]
Java
1import java.util.*; 2 3public class CourseSchedule { 4 5 // Returns valid course order, or empty list if impossible 6 public static int[] findOrder(int numCourses, int[][] prerequisites) { 7 List<List<Integer>> graph = new ArrayList<>(); 8 int[] inDegree = new int[numCourses]; 9 10 for (int i = 0; i < numCourses; i++) graph.add(new ArrayList<>()); 11 12 // Build graph: prerequisite → course 13 for (int[] pre : prerequisites) { 14 int course = pre[0], prereq = pre[1]; 15 graph.get(prereq).add(course); // prereq → course 16 inDegree[course]++; 17 } 18 19 // Kahn's topological sort 20 Queue<Integer> queue = new LinkedList<>(); 21 for (int i = 0; i < numCourses; i++) { 22 if (inDegree[i] == 0) queue.offer(i); 23 } 24 25 int[] order = new int[numCourses]; 26 int idx = 0; 27 28 while (!queue.isEmpty()) { 29 int u = queue.poll(); 30 order[idx++] = u; 31 32 for (int v : graph.get(u)) { 33 if (--inDegree[v] == 0) queue.offer(v); 34 } 35 } 36 37 // If all courses processed → valid order; else cycle → impossible 38 return idx == numCourses ? order : new int[]{}; 39 } 40 41 public static void main(String[] args) { 42 // Can finish all 4 courses? 43 int[][] prereqs1 = {{1,0},{2,0},{3,1},{3,2}}; 44 int[] result1 = findOrder(4, prereqs1); 45 System.out.println("Order: " + Arrays.toString(result1)); 46 // [0, 1, 2, 3] or [0, 2, 1, 3] 47 48 // Impossible: 0↔1 form a cycle 49 int[][] prereqs2 = {{1,0},{0,1}}; 50 int[] result2 = findOrder(2, prereqs2); 51 System.out.println("Order: " + Arrays.toString(result2)); // [] 52 } 53}
Output:
Order: [0, 1, 2, 3]
Order: []   (impossible — cycle)

DFS vs Kahn's — Side by Side

SAME DAG: 5→0, 5→2, 4→0, 4→1, 2→3, 3→1

          DFS (post-order + reverse)      Kahn's (BFS in-degree)
  ─────────────────────────────────────────────────────────────
  Result  [5, 4, 2, 3, 1, 0]             [4, 5, 0, 2, 3, 1]
  Both are valid topological orderings ✓

COMPARISON:
  Feature                   DFS Approach       Kahn's Approach
  ─────────────────────────────────────────────────────────────
  Traversal style           Recursive DFS      Iterative BFS
  Data structure            Stack + call stack Queue
  Cycle detection           Separate check     Built-in (count < V)
  Result                    Push at DFS exit   Append at dequeue
  Order                     Reverse of push    Direct append
  Disconnected graphs       Outer loop needed  Outer loop NOT needed
  Recursion limit risk      Yes (deep graphs)  No (iterative)
  Memory                    O(V) stack         O(V) queue
  Preferred for             Path-based logic   Scheduling, cycle check
  Time complexity           O(V + E)           O(V + E)

WHEN TO CHOOSE DFS:
  ✓ Problem involves DFS path tracking simultaneously
  ✓ Need to identify back edges / strongly connected components
  ✓ Recursive solution is cleaner for the problem structure

WHEN TO CHOOSE KAHN'S:
  ✓ Cycle detection is also needed (count < V tells you)
  ✓ Avoiding recursion stack overflow (large graphs)
  ✓ Course scheduling, build systems — natural "prerequisite" model
  ✓ Want to process vertices at same level simultaneously

Complexity Summary

ApproachTimeSpaceNotes
DFS post-orderO(V + E)O(V)Call stack + result stack
Kahn's BFSO(V + E)O(V)In-degree array + queue

Both visit each vertex once and each edge once — O(V + E) total.

Space: O(V) for visited array, stack/queue, and in-degree array.

Common Mistakes

DFS: pushing vertex at entry (pre-order) instead of exit (post-order). If you push u when you first visit it, u goes to the stack before any of its dependencies. After reversal, u appears after its dependencies — backwards! Always push AFTER all recursive calls complete (post-order = DFS exit).

Kahn's: not building the graph correctly for course schedule problems. If prerequisites[i] = [a, b] means "take b before a", the edge must be b → a (prerequisite → dependent). Building a → b reverses the dependency and gives wrong results. The in-degree of the dependent (a) must be incremented, not the prerequisite (b).

Forgetting the outer loop in DFS for disconnected DAGs. DFS from one vertex only covers its reachable component. If the DAG has isolated vertices or separate components, the outer loop for i in range(V): if not visited[i]: dfs(i) is essential. Kahn's doesn't need this — its initial scan over all vertices handles all components.

Kahn's: not checking result.size() == V for cycle detection. If you skip this check and just return result, a cyclic graph silently returns a partial ordering of non-cycle vertices. Always verify the count equals V — if not, the missing vertices form a cycle and no valid topological order exists.

Multiple valid orderings: assuming uniqueness. Topological sort is not unique when multiple vertices have in-degree 0 simultaneously. If the problem requires a specific valid ordering (e.g., lexicographically smallest), use a min-heap (priority queue) instead of a regular queue in Kahn's.

Interview Questions

Q: Why does topological sort only work on DAGs?

Topological sort requires placing every edge u→v such that u comes before v. In a cycle A→B→C→A: A must come before B, B before C, and C before A — which requires A to come both before and after B simultaneously. This contradiction makes any linear ordering impossible. No valid topological order exists for cyclic directed graphs.

Q: How does Kahn's algorithm detect cycles as a side effect?

Kahn's processes vertices in topological order by always picking a vertex with in-degree 0. In a directed cycle, every cycle vertex always has at least one incoming edge from another cycle vertex — its in-degree never reaches 0 while cycle members remain. These vertices are never enqueued, never processed, never counted. After Kahn's completes, if processed count < V, those unprocessed vertices form a cycle.

Q: When does Kahn's produce a unique topological ordering?

Kahn's produces a unique ordering if and only if, at every step, exactly one vertex has in-degree 0 in the remaining unprocessed subgraph. This happens when the DAG is a Hamiltonian path — a single directed chain where each vertex has exactly one predecessor and one successor. If at any point multiple vertices have in-degree 0, they can be ordered in any way — the topological sort is non-unique.

Summary

Topological sort linearly orders vertices of a DAG such that every edge u→v has u before v. Only works on Directed Acyclic Graphs.

Two approaches — both O(V + E):

DFS (post-order):

  1. Run DFS; push each vertex to stack AFTER all its descendants finish
  2. Pop the stack — this gives topological order
  3. Outer loop ensures all components are covered

Kahn's (BFS / in-degree):

  1. Compute in-degree for all vertices
  2. Enqueue all zero in-degree vertices
  3. BFS: process, decrement neighbours' in-degrees, enqueue newly zeroed vertices
  4. Check: if processed count < V → cycle exists
DFSKahn's
StyleRecursiveIterative
Cycle detectionSeparate step neededBuilt-in (count < V)
RiskStack overflow for deep graphsNone
Best forPath-tracking problemsScheduling, cycle check, large graphs

Classic problems: Course Schedule I/II, Alien Dictionary, Build Order, Task Dependencies.

In the next topic you will explore Shortest Paths — Dijkstra's algorithm for positive-weight graphs and Bellman-Ford for negative weights.

Suggested Quiz

Topological sort is ONLY possible on which type of graph?

1/6
Topological Sort