Jump to section

A topological sort orders the nodes of a directed graph so every edge points forwards, for example taking each course only after its prerequisites. Kahn's algorithm repeatedly takes nodes with no remaining incoming edges, and a depth-first search reverses the finishing order. Both run in O(V + E) time and detect cycles.

The Problem

You have n courses numbered 0 to n โˆ’ 1 and a list of prerequisite pairs. A pair [a, b] means you must finish course b before course a. Return an order in which you can take every course, or an empty list if it is impossible because the prerequisites form a cycle.

This is topological sorting in disguise: courses are nodes, each pair is a directed edge from b to a, and a valid order exists only if the graph has no cycle (it is a DAG, a directed acyclic graph).

Example 1

Input: n = 4, [[1, 0], [2, 0], [3, 1], [3, 2]]. Output: [0, 1, 2, 3]. Course 0 has no prerequisites, courses 1 and 2 need 0, and course 3 needs both 1 and 2. [0, 2, 1, 3] is also correct: there is often more than one valid order.

Example 2

Input: n = 2, [[0, 1], [1, 0]]. Output: []. Each course needs the other first, which is a cycle, so no order works.

Assumptions to confirm

  • Is any valid order accepted, or a specific one, such as the smallest number first?
  • Can there be duplicate pairs or courses with no prerequisites at all? The solutions handle both.
  • Should a cycle return an empty list, raise an error, or just return false? This is the "can you finish all courses" variant.

The Naive Approach

The obvious approach is to repeatedly scan all courses for one whose prerequisites are all taken, take it, and repeat. Each scan is O(V + E) and you do up to V scans, so it is O(V ร— (V + E)). It is a fine starting point to say out loud, because the optimal version is the same idea with bookkeeping: instead of rescanning, keep a count of remaining prerequisites per course and a queue of courses that are ready.

Ready to put this into practice?

Practice this with MockIF →

Still applying for jobs? Let Auto Apply do it

The Optimal Approach: Kahn's Algorithm

1

Build the graph and the in-degrees

For each pair [a, b], add an edge from b to a and add one to the in-degree of a. In-degree is the number of prerequisites still outstanding.

2

Queue every node with in-degree 0

These courses can be taken right now.

3

Take nodes off the queue in turn

Append each one to the order, then subtract one from the in-degree of every course that depends on it. Any course that drops to 0 joins the queue.

4

Check for a cycle at the end

If the order contains every node, it is valid. If some nodes never reached in-degree 0, they are stuck in a cycle, so return an empty list.

Python and TypeScript Solutions

Kahn's algorithm in Python

from collections import deque


def course_order(num_courses: int, prerequisites: list[list[int]]) -> list[int]:
    """Kahn's algorithm. [a, b] means 'take b before a'. Returns [] if there is a cycle."""
    graph: list[list[int]] = [[] for _ in range(num_courses)]
    indegree = [0] * num_courses
    for course, before in prerequisites:
        graph[before].append(course)
        indegree[course] += 1

    queue = deque(c for c in range(num_courses) if indegree[c] == 0)
    order = []
    while queue:
        node = queue.popleft()
        order.append(node)
        for nxt in graph[node]:
            indegree[nxt] -= 1
            if indegree[nxt] == 0:
                queue.append(nxt)
    return order if len(order) == num_courses else []

Kahn's algorithm in TypeScript

The queue uses a moving head index instead of shift(), which is O(n) on JavaScript arrays and would quietly make the whole algorithm quadratic.

// Kahn's algorithm. [a, b] means "take b before a". Returns [] if there is a cycle.
function courseOrder(numCourses: number, prerequisites: number[][]): number[] {
  const graph: number[][] = Array.from({ length: numCourses }, () => []);
  const indegree = new Array<number>(numCourses).fill(0);
  for (const [course, before] of prerequisites) {
    graph[before].push(course);
    indegree[course]++;
  }

  const queue: number[] = [];
  for (let c = 0; c < numCourses; c++) if (indegree[c] === 0) queue.push(c);

  const order: number[] = [];
  for (let head = 0; head < queue.length; head++) { // index pointer instead of shift(): O(1)
    const node = queue[head];
    order.push(node);
    for (const next of graph[node]) {
      if (--indegree[next] === 0) queue.push(next);
    }
  }
  return order.length === numCourses ? order : [];
}

The DFS approach

Depth-first search gives a second valid answer. Each node is marked as unvisited, on the current path, or done. Meeting a node that is still on the current path means you found a cycle. A node is added to the list only after everything that depends on it is finished, so reversing that list gives a topological order.

def course_order_dfs(num_courses: int, prerequisites: list[list[int]]) -> list[int]:
    """DFS with three colours: 0 unvisited, 1 on the current path, 2 done."""
    graph: list[list[int]] = [[] for _ in range(num_courses)]
    for course, before in prerequisites:
        graph[before].append(course)

    state = [0] * num_courses
    post_order: list[int] = []

    def visit(node: int) -> bool:
        if state[node] == 1:
            return False              # back edge: a cycle
        if state[node] == 2:
            return True
        state[node] = 1
        for nxt in graph[node]:
            if not visit(nxt):
                return False
        state[node] = 2
        post_order.append(node)       # added after everything that depends on it
        return True

    for c in range(num_courses):
        if not visit(c):
            return []
    return post_order[::-1]
// DFS with three states: 0 unvisited, 1 on the current path, 2 done.
function courseOrderDfs(numCourses: number, prerequisites: number[][]): number[] {
  const graph: number[][] = Array.from({ length: numCourses }, () => []);
  for (const [course, before] of prerequisites) graph[before].push(course);

  const state = new Array<number>(numCourses).fill(0);
  const postOrder: number[] = [];

  const visit = (node: number): boolean => {
    if (state[node] === 1) return false; // back edge: a cycle
    if (state[node] === 2) return true;
    state[node] = 1;
    for (const next of graph[node]) if (!visit(next)) return false;
    state[node] = 2;
    postOrder.push(node); // added after everything that depends on it
    return true;
  };

  for (let c = 0; c < numCourses; c++) if (!visit(c)) return [];
  return postOrder.reverse();
}

The DFS version is recursive, so a graph with a very long chain can hit the recursion limit, especially in Python. That is a good reason to lead with Kahn's algorithm in an interview.

Ready to put this into practice?

Practice this with MockIF →

Still applying for jobs? Let Auto Apply do it

Kahn's Algorithm vs DFS

Kahn's algorithm (BFS)DFS
TimeO(V + E)O(V + E)
Extra spaceO(V + E) for the graph, in-degrees and queueO(V + E) for the graph, states and call stack
Cycle detectionFewer than V nodes in the orderMeeting a node still on the current path
IterativeYesRecursive unless you manage a stack yourself
Easy to extendYes: swap the queue for a min-heap to get the smallest orderLess natural for ordering rules

How to Talk Through It in an Interview

  1. Clarify: "Pair [a, b] means b comes before a? Any valid order is fine? A cycle returns an empty list?"
  2. Model it: "Courses are nodes and prerequisites are directed edges. I need a topological order, which exists only if there is no cycle."
  3. Naive: "I could keep rescanning for a course with nothing left to wait for, but that is O(V ร— (V + E))."
  4. Optimise: "Kahn's algorithm tracks in-degrees and a queue of ready courses, so each node and edge is handled once: O(V + E)."
  5. Test: the four-course example, the two-course cycle, a graph with no edges and a single course.

Ready to put this into practice?

Practice this with MockIF →

Still applying for jobs? Let Auto Apply do it

Edge Cases and Follow-Ups

Courses with no prerequisites

They start with in-degree 0 and go straight into the queue, so a graph with no edges returns every course.

A cycle not involving every node

Kahn's algorithm still outputs the courses outside the cycle, which is why you compare the order's length with n.

Follow-up: return true or false only

Same algorithm, return whether the order has length n. This is the "can you finish all courses" version.

Follow-up: smallest valid order

Replace the queue with a min-heap so the smallest ready course always comes next. Time becomes O(V log V + E).

Follow-up: alien dictionary

Comparing neighbouring words gives ordering edges between letters, then a topological sort gives the alphabet. It is a popular harder variant.

Practise It in a Mock Coding Round

Reading a solution is not the same as producing it with someone watching. In a real round you have to clarify the problem, say the brute force out loud, and keep talking while you type. MockIF runs coding rounds with a voice AI interviewer in a real in-browser editor, in Python and TypeScript. It runs your code against tests, asks you to explain your approach, follows up on edge cases and complexity, then scores the round and gives you specific feedback. A session costs 2 credits. It is free to start with 5 credits, then monthly or 3-monthly plans, or one-off credit packs (see pricing). Read more about how a mock coding interview works.

Frequently Asked Questions

What is Kahn's algorithm?
Kahn's algorithm is a breadth-first way to topologically sort a directed graph. It repeatedly removes nodes with no incoming edges, updating the in-degrees of their neighbours, and detects a cycle if some nodes are never removed.
What is the time complexity of topological sort?
O(V + E) for both Kahn's algorithm and the DFS approach, where V is the number of nodes and E the number of edges. Each node and edge is processed once.
Can a graph have more than one topological order?
Yes. Whenever two nodes are ready at the same time, either can come first. Only a graph that forms a single chain has exactly one order.
How does topological sort detect a cycle?
In Kahn's algorithm, a cycle leaves some nodes with in-degree above zero, so the order is shorter than the number of nodes. In DFS, reaching a node that is still on the current path means there is a cycle.
Should I use DFS or Kahn's algorithm in an interview?
Kahn's algorithm is usually the safer choice: it is iterative, detects cycles with one length check and is easy to explain. Mention DFS as an alternative to show range.

Stop Preparing in Your Head. Start Practicing Out Loud.

Drop your resume, add a job description, and get a mock interview like the real thing.

Start Free Mock Interview →
No credit card required. 5 free credits to start.

Still applying for jobs? Let Auto Apply do it for you.

Try Auto Apply →