✒️ Quill

Topological Sort, Two Ways

by ada · 2026-05-01 · edited 2026-05-01

Topological Sort, Two Ways

A topological order on a DAG is any ordering of the vertices where every edge u → v has u before v. Build systems, course prerequisites, package managers, schedulers — all topological sorts in disguise.

There's no topological order if the graph has a cycle. The algorithms below detect that.

Method 1: Kahn's algorithm (BFS)

Repeatedly: pick a vertex with no incoming edges, output it, remove it from the graph.

from collections import deque

def kahn(graph, n):
    indeg = [0] * n
    for u in range(n):
        for v in graph[u]:
            indeg[v] += 1

    q = deque(u for u in range(n) if indeg[u] == 0)
    order = []
    while q:
        u = q.popleft()
        order.append(u)
        for v in graph[u]:
            indeg[v] -= 1
            if indeg[v] == 0:
                q.append(v)
    if len(order) != n:
        raise ValueError("graph has a cycle")
    return order

This is O(V + E). The cycle detection is free: if any vertex still has incoming edges at the end, the graph isn't a DAG.

Method 2: DFS with finishing times

Run DFS. Whenever you finish processing a node (i.e., return from its recursive call), prepend it to the output. The reverse of finishing order is a topological order.

def dfs_topo(graph, n):
    WHITE, GRAY, BLACK = 0, 1, 2
    color = [WHITE] * n
    order = []

    def visit(u):
        if color[u] == GRAY:
            raise ValueError("cycle detected")
        if color[u] == BLACK:
            return
        color[u] = GRAY
        for v in graph[u]:
            visit(v)
        color[u] = BLACK
        order.append(u)

    for u in range(n):
        if color[u] == WHITE:
            visit(u)
    return order[::-1]

The three-color scheme is the classic cycle-detection idiom: WHITE = unvisited, GRAY = on the current DFS path, BLACK = fully processed. If you ever traverse to a GRAY vertex, you've found a back-edge — a cycle.

Pick whichever fits the constraint

Kahn's algorithm is iterative and gives you a clean place to plug in priority (e.g., always process tasks with shorter duration first when ties exist). DFS-based topo sort is shorter to write and natural when you're already in a DFS-style traversal.