DSATopological Sort

Topological Sort

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

Two critical constraints:

  • Topological sort is only defined for Directed Acyclic Graphs (DAGs).
  • If the graph contains a cycle, no valid ordering exists.
Real-World Intuition

Imagine you have a list of courses and prerequisites. You cannot take "Machine Learning" before you have passed "Linear Algebra". Topological sort gives you a valid study order.

Other examples:

  • Dependency resolution (npm install runs packages in dependency order)
  • Build systems (compile files whose imports are compiled first)
  • Task scheduling (run task B only after task A finishes)
Approach 1 — Kahn's Algorithm (BFS)

Kahn's algorithm uses the concept of in-degree — the number of incoming edges to a vertex.

  1. Compute in-degree for every vertex.
  2. Add all vertices with in-degree 0 to a queue (they have no prerequisites).
  3. Process the queue: for each dequeued vertex, add it to the result and decrease the in-degree of each neighbour. If a neighbour reaches in-degree 0, enqueue it.
  4. If the result contains all vertices → valid topological order. If not (some vertices remain) → the graph has a cycle.

JS
function topologicalSortBFS(numVertices, edges) {
  // Build adjacency list and in-degree array
  const graph    = Array.from({ length: numVertices }, () => []);
  const inDegree = new Array(numVertices).fill(0);

  for (const [u, v] of edges) {
    graph[u].push(v);
    inDegree[v]++;
  }

  // Queue: all vertices with no prerequisites
  const queue = [];
  for (let i = 0; i < numVertices; i++) {
    if (inDegree[i] === 0) queue.push(i);
  }

  const order = [];
  while (queue.length > 0) {
    const node = queue.shift(); // O(n) — use a real queue for production
    order.push(node);
    for (const neighbour of graph[node]) {
      inDegree[neighbour]--;
      if (inDegree[neighbour] === 0) queue.push(neighbour);
    }
  }

  // Cycle detection: if order doesn't include all vertices, cycle exists
  return order.length === numVertices ? order : [];
}

// 5 courses, prerequisites: 1→0, 2→0, 3→1, 3→2, 4→3
const edges = [[1,0],[2,0],[3,1],[3,2],[4,3]];
console.log(topologicalSortBFS(5, edges)); // [0,1,2,3,4] or similar valid order
Note
Time: O(V + E). Space: O(V + E) for adjacency list and in-degree array. Kahn's algorithm naturally detects cycles — if the output size is less than V, a cycle exists.
Approach 2 — DFS with Finish Timestamps

Run DFS on each unvisited node. A node is added to the front of the result list only after all its descendants have been fully explored (post-order). This guarantees that dependencies come before dependents.

Track three states per node:

  • 0 = unvisited
  • 1 = currently being visited (in the DFS call stack)
  • 2 = fully processed

If you reach a node with state 1, you have found a back edge → cycle detected.

JS
function topologicalSortDFS(numVertices, edges) {
  const graph = Array.from({ length: numVertices }, () => []);
  for (const [u, v] of edges) graph[u].push(v);

  const state = new Array(numVertices).fill(0); // 0=unvisited,1=visiting,2=done
  const order = [];
  let hasCycle = false;

  function dfs(node) {
    if (state[node] === 1) { hasCycle = true; return; } // back edge = cycle
    if (state[node] === 2) return;                        // already processed

    state[node] = 1; // mark as being visited
    for (const neighbour of graph[node]) {
      dfs(neighbour);
      if (hasCycle) return;
    }
    state[node] = 2;       // done
    order.unshift(node);   // prepend — post-order gives reverse topological sort
  }

  for (let i = 0; i < numVertices; i++) {
    if (state[i] === 0) dfs(i);
    if (hasCycle) return [];
  }
  return order;
}

const edges2 = [[1,0],[2,0],[3,1],[3,2],[4,3]];
console.log(topologicalSortDFS(5, edges2)); // [4,3,1,2,0] or similar
Warning
unshift() is O(n) per call; replace the result array with a stack (push then reverse) for O(1) prepend.
Problem 1 — Course Schedule I (LeetCode 207)

Can you finish all courses given their prerequisites? Equivalent to: "does the graph have a cycle?"

JS
function canFinish(numCourses, prerequisites) {
  const graph    = Array.from({ length: numCourses }, () => []);
  const inDegree = new Array(numCourses).fill(0);

  for (const [course, pre] of prerequisites) {
    graph[pre].push(course);
    inDegree[course]++;
  }

  const queue = [];
  for (let i = 0; i < numCourses; i++) {
    if (inDegree[i] === 0) queue.push(i);
  }

  let completed = 0;
  while (queue.length > 0) {
    const node = queue.shift();
    completed++;
    for (const next of graph[node]) {
      if (--inDegree[next] === 0) queue.push(next);
    }
  }
  return completed === numCourses;
}

console.log(canFinish(2, [[1,0]]));       // true  (take 0, then 1)
console.log(canFinish(2, [[1,0],[0,1]])); // false (cycle: 0↔1)
Problem 2 — Course Schedule II (LeetCode 210)

Return one valid course ordering, or an empty array if impossible. Same as Kahn's algorithm — just return the order array.

JS
function findOrder(numCourses, prerequisites) {
  const graph    = Array.from({ length: numCourses }, () => []);
  const inDegree = new Array(numCourses).fill(0);

  for (const [course, pre] of prerequisites) {
    graph[pre].push(course);
    inDegree[course]++;
  }

  const queue = [];
  for (let i = 0; i < numCourses; i++) {
    if (inDegree[i] === 0) queue.push(i);
  }

  const order = [];
  while (queue.length > 0) {
    const node = queue.shift();
    order.push(node);
    for (const next of graph[node]) {
      if (--inDegree[next] === 0) queue.push(next);
    }
  }
  return order.length === numCourses ? order : [];
}

console.log(findOrder(4, [[1,0],[2,0],[3,1],[3,2]]));
// [0,1,2,3] or [0,2,1,3]
Problem 3 — Alien Dictionary (LeetCode 269)

Given a sorted list of alien-language words, deduce the character ordering of the alien alphabet.

  1. Compare adjacent words character by character. The first differing character gives an edge: char1 → char2 in the alien alphabet.
  2. Run Kahn's algorithm on these edges. If a cycle is found or not all characters appear, the input is invalid.

JS
function alienOrder(words) {
  const graph    = new Map();
  const inDegree = new Map();

  // Initialise graph with all unique characters
  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);
    }
  }

  // Build edges from adjacent word comparisons
  for (let i = 0; i < words.length - 1; i++) {
    const w1 = words[i], w2 = words[i + 1];
    const minLen = Math.min(w1.length, w2.length);
    // Edge case: "abc" before "ab" is invalid
    if (w1.length > w2.length && w1.startsWith(w2)) return '';
    for (let j = 0; j < minLen; j++) {
      if (w1[j] !== 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]) + 1);
        }
        break; // only first differing char matters
      }
    }
  }

  // Kahn's algorithm
  const queue = [];
  for (const [ch, deg] of inDegree) if (deg === 0) queue.push(ch);

  let result = '';
  while (queue.length > 0) {
    const ch = queue.shift();
    result += ch;
    for (const next of graph.get(ch)) {
      inDegree.set(next, inDegree.get(next) - 1);
      if (inDegree.get(next) === 0) queue.push(next);
    }
  }
  return result.length === inDegree.size ? result : '';
}

console.log(alienOrder(['wrt','wrf','er','ett','rftt'])); // 'wertf'
Complexity Summary

Algorithm

Time

Space

Notes

Kahn's (BFS)

O(V + E)

O(V + E)

Easy cycle detection; iterative

DFS post-order

O(V + E)

O(V + E)

Natural recursion; watch stack depth

Practice Problems
  • LeetCode 207 — Course Schedule I (cycle detection)

  • LeetCode 210 — Course Schedule II (return order)

  • LeetCode 269 — Alien Dictionary

  • LeetCode 310 — Minimum Height Trees (iterative leaf trimming = topo sort)

  • LeetCode 444 — Sequence Reconstruction

  • LeetCode 2115 — Find All Possible Recipes from Given Supplies