Home › Graphs › Traversal & ordering
algorithm low

Topological sort

Problem A system that runs, computes or installs items that depend on each other must find an order in which every item comes after what it needs, and detect circular dependencies that make this impossible.

Order the vertices of a DAG so every edge points forward; the core of build systems, schedulers and dependency resolution, with cycle detection for free.

O(V + E) time and memory (Kahn or DFS); O((V + E) log V) for the lexicographically smallest order
dagdependenciesschedulingorderingcycle-detection
Overview

A topological order lists the vertices so that for every edge u → v, u comes before v. That is exactly an execution order for tasks with dependencies. Kahn's algorithm repeatedly removes a vertex with no remaining incoming edges. The DFS method outputs vertices in reverse finish order. If some vertices are never emitted, they lie on or behind a cycle.

Key facts

Use when

  • Anything that must run, compute or install in dependency order:
    • build systems and package installs: topological schedulers such as make (Mokhov et al. 2018), init and service ordering;
    • data pipelines: Airflow DAGs of tasks (docs), Spark job stages (Zaharia et al. 2012);
    • spreadsheets: recalculation in dependency order (Excel calculation chain);
    • machine learning: the backward pass of autograd runs a node once all its dependencies are done (PyTorch engine);
    • chip design: timing analysis visits gates in dependency order (Hitchcock, Smith, Cheng 1982);
    • DP over a DAG: shortest or longest paths, path counting.

Avoid when

  • The graph has cycles you must handle: condense its SCCs first (Tarjan), then sort the condensation.
  • Edges change constantly: use an incremental algorithm (Pearce-Kelly) instead of resorting each time.

Deep dive

How it works

In: directed graph as adjacency lists (edge u → v: u must come first) · Out: an order in which every edge points forward, or the vertices on or behind a cycle · State: none between calls; per call an in-degree array and the output order · Cost: O(V + E) time and memory

  1. Compute indeg[v] for all v.
  2. Ready = all vertices with in-degree 0.
  3. Pop u from ready and append it to the order. For each u → v, decrement indeg[v], and if it reaches 0, push v.
  4. If the order has fewer than V vertices, those with indeg > 0 are on or behind a cycle. (This is Kahn's algorithm.)
  5. Alternative: run DFS. When a vertex finishes (all its descendants are done), append it to the postorder, then reverse. A gray-to-gray edge (back edge) means a cycle.
a → b → d          indeg: a0 b1 c1 d2
a → c → d          Kahn: [a] → [b c] → [d]       waves: {a} {b,c} {d}

Code

/// Kahn's algorithm. Ok(order) or Err(vertices that are on or downstream of a cycle).
pub fn topo_sort(adj: &[Vec<u32>]) -> Result<Vec<u32>, Vec<u32>> {
    let n = adj.len();
    let mut indeg = vec![0u32; n];
    for vs in adj { for &v in vs { indeg[v as usize] += 1; } }
    let mut order: Vec<u32> = (0..n as u32).filter(|&v| indeg[v as usize] == 0).collect();
    let mut head = 0; // `order` doubles as the FIFO queue
    while head < order.len() {
        let u = order[head];
        head += 1;
        for &v in &adj[u as usize] {
            indeg[v as usize] -= 1;
            if indeg[v as usize] == 0 { order.push(v); }
        }
    }
    if order.len() == n {
        Ok(order)
    } else {
        Err((0..n as u32).filter(|&v| indeg[v as usize] > 0).collect())
    }
}

/// Wave per task (same wave = can run in parallel) and the critical-path length for given durations.
pub fn schedule(adj: &[Vec<u32>], dur: &[u64]) -> Option<(Vec<u32>, u64)> {
    let order = topo_sort(adj).ok()?;
    let mut wave = vec![0u32; adj.len()];
    let mut finish = vec![0u64; adj.len()]; // holds the latest predecessor finish until u is processed
    for &u in &order {
        let u = u as usize;
        finish[u] += dur[u];
        for &v in &adj[u] {
            let v = v as usize;
            wave[v] = wave[v].max(wave[u] + 1);
            finish[v] = finish[v].max(finish[u]);
        }
    }
    Some((wave, finish.into_iter().max().unwrap_or(0)))
}

/// Deterministic order: always take the smallest ready vertex.
pub fn topo_sort_lex(adj: &[Vec<u32>]) -> Option<Vec<u32>> {
    use std::{cmp::Reverse, collections::BinaryHeap};
    let mut indeg = vec![0u32; adj.len()];
    for vs in adj { for &v in vs { indeg[v as usize] += 1; } }
    let mut ready: BinaryHeap<Reverse<u32>> =
        (0..adj.len() as u32).filter(|&v| indeg[v as usize] == 0).map(Reverse).collect();
    let mut order = Vec::with_capacity(adj.len());
    while let Some(Reverse(u)) = ready.pop() {
        order.push(u);
        for &v in &adj[u as usize] {
            indeg[v as usize] -= 1;
            if indeg[v as usize] == 0 { ready.push(Reverse(v)); }
        }
    }
    (order.len() == adj.len()).then_some(order)
}

Complexity and constants

  • Kahn: one pass to count in-degrees and one pass over the edges. 4 B per vertex for indeg, and the output doubles as the queue.
  • On CSR graphs that fit in cache, order of 1-10 ns per edge. On larger graphs it is bound by random indeg[v] updates, about one cache miss per edge.
  • Heap-based lexicographic order: O(log V) per vertex extra, fine up to about 10⁷ vertices.
  • Critical-path scheduling with unlimited workers finishes in time = longest path. With p workers, list scheduling is within 2× of optimal (Graham).

Efficiency

  • Cost: measured with the code above on an Apple M4 Pro, one thread, random DAG with 2^20 vertices and 8 · 2^20 edges as Vec<Vec<u32>>, best of 3: Kahn 122 ms (14.6 ns per edge); with waves and critical path (schedule) 234 ms; lexicographic order with a heap 135 ms. Memory: 4 B per vertex for indeg, the output doubles as the queue. Bound: random indeg[v] updates (cache misses).
  • Levers:
    • plain Kahn instead of the heap for a deterministic order — 1.11× (135 vs 122 ms; measured) — only pay when stable output matters
    • CSR instead of Vec<Vec<u32>> — 1.56× for BFS at 2^20 vertices (measured on BFS/DFS, not on Kahn) — graphs that fit in cache
    • atomic in-degree counters and a work queue — no global sort, the order emerges while tasks run (Implementation notes) — parallel executors
    • critical-path-first among ready tasks — list scheduling stays within 2× of optimal (Graham 1969) — p workers, wide graphs
    • Pearce-Kelly order maintenance — reorders only the region between the endpoints (Pearce, Kelly 2006) — graphs that change by edge insertion
  • Naive → standard → state of the art:
    • rescan all vertices for one with no unprocessed predecessor · O(V²) at least
    • Kahn with a queue (code above) · 122 ms, 14.6 ns per edge on the graph above
    • parallel executor with atomic counters, incremental order maintenance · work per edge stays O(1) (Implementation notes)

Implementation notes

  • Parallel executor (make -j, Bazel, Cargo): keep atomic in-degree counters. Each worker finishes a task, decrements its successors, and pushes those that reach 0 onto a work queue (work-stealing deque). No global sort is needed, and the order emerges dynamically.
  • Prioritize the critical path: among ready tasks, start the one with the longest remaining path to a sink first. This often cuts wall time on wide builds.
  • Cycle reporting: Kahn tells you that there is a cycle. To show the user which one, run DFS on the leftover vertices and print the gray stack when a back edge appears.
  • Stable output: iterate over sorted vertex IDs or use the heap version. HashMap iteration order makes builds and lockfiles nondeterministic.
  • Duplicate edges are fine as long as counting and decrementing are consistent.
  • Incremental: Pearce-Kelly maintains an order under edge insertions and only reorders the affected region between the two endpoints.

Pitfalls

  • Ignoring the length check: Kahn silently returns a partial order on cyclic input.
  • Recursive DFS on deep dependency chains overflows the stack. Use Kahn or an explicit stack.
  • Recomputing the whole order on every small change in an interactive system (spreadsheets, reactive UIs). Use incremental or dirty-marking schemes.
  • Assuming the order is unique and caching it as "the" order: any tie-break change gives a different valid order.
  • Edge direction confusion ("depends on" vs "is needed by"). Reversing the edges reverses the order. Test with a 3-node chain.

Variants and alternatives

DFS reverse postorderno in-degree array, gives cycles with their path.
Coffman-Grahamscheduling with a fixed number of processors (layered graph drawing too).
SCC condensation (Tarjan)makes any directed graph a DAG of components first.
DAG shortest/longest pathsrelax edges in topological order. Negative weights are fine and no heap is needed, unlike Dijkstra.

In production

Build and install: make, Ninja, Bazel, Cargo, npm/pip/apt dependency resolution (ordering step), systemd unit ordering. Data pipelines: Airflow and Dagster DAGs, Spark DAGScheduler stages (Zaharia et al. 2012). Machine learning: PyTorch autograd engine (dependency counts for the backward pass), TensorFlow graph execution. Spreadsheets: recalculation engines (Excel). Chip design: static timing analysis (Hitchcock et al. 1982).

Implementations

namelangrepoinstallverdictlicencehealthas_ofnotes
petgraphRustpetgraph/petgraphcargo add petgraph————algo::toposort (DFS), returns the node on a cycle as Err
pathfindingRustevenfurther/pathfindingcargo add pathfinding————directed::topological_sort over nodes and a successor closure
rustworkxPython / RustQiskit/rustworkxpip install rustworkx————topological_sort, topological_generations on a Rust graph core
Boost Graph LibraryC++boostorg/graphbrew install boost————topological_sort
NetworkXPythonnetworkx/networkxpip install networkx————topological_sort, topological_generations; for prototypes

Sources

  • A. B. Kahn, Topological Sorting of Large Networks, CACM, 1962. paper
  • D. Knuth, The Art of Computer Programming, Vol. 1, §2.2.3, 1968 (3rd ed. 1997).
  • R. Tarjan, Depth-First Search and Linear Graph Algorithms, SIAM Journal on Computing, 1972. paper
  • D. Pearce, P. Kelly, A Dynamic Topological Sort Algorithm for Directed Acyclic Graphs, ACM Journal of Experimental Algorithmics, 2006. paper
  • R. Graham, Bounds on Multiprocessing Timing Anomalies, SIAM Journal on Applied Mathematics, 1969. paper
  • A. Mokhov, N. Mitchell, S. Peyton Jones, Build Systems à la Carte, ICFP 2018. paper
  • R. Hitchcock, G. Smith, D. Cheng, Timing Analysis of Computer Hardware, IBM Journal of Research and Development 26(1), 1982. paper

Builds on

Used by

audio mixing graph · plannedDAG of voices, buses and effects rendered per block on the audio thread without locks or allocation
bill of materials explosion · plannedMRP netting: explode BOMs level by level with lead-time offsetting into planned orders
critical path analysis · plannedLongest dependency chain in a DAG of timed spans or build actions
DAG workflow executionMany jobs (builds, data loads, training steps) depend on each other's outputs and must run in a valid order on limited workers, survive failures, and on a rerun redo only what changed.
data lineage tracking · plannedRecording which inputs and transforms produced each output value for audit and reruns
Dataflow analysisOptimizers and checkers must know facts that hold at every point of a function, such as which variables may still be read or which values were already computed. This is for computing such whole-function facts.
datalog evaluation · plannedSemi-naive bottom-up evaluation of recursive Datalog rules
declarative diff plan · plannedDiff desired vs actual state into an ordered plan of create/update/delete actions
dependency version resolution · plannedPick package versions that satisfy semver constraints (PubGrub, SAT) with readable conflict errors
Signals and fine-grained reactivitySystems that show derived values (screens, spreadsheets, notebooks, build graphs) must update them when inputs change; recomputing and diffing everything wastes time when state is large and changes often.
incremental computation · plannedDependency graph of derived values recomputed only when inputs change (reactive cells, spreadsheets)
Infrastructure as codeTeams that create servers and cloud resources by clicking in a console cannot reproduce environments, review changes or recover quickly from disaster. This guide covers managing infrastructure as reviewed files and its risks.
instruction scheduling · plannedList scheduling over dependence DAGs with latency models; software pipelining of loops
layout analysis · plannedDetecting page regions (text blocks, tables, figures) and reading order in documents
lineage based recovery · plannedRecompute lost partitions from their recorded transformation lineage instead of replicating
operator graph partitioning · plannedSplitting a model graph across CPU/GPU/NPU backends by supported ops and transfer cost
plugin delay compensation · plannedAlign parallel audio paths by reported processing latency
quantum circuit optimization · plannedGate cancellation, commutation and resynthesis passes over circuit DAGs
qubit routing · plannedMap logical qubits to a device coupling graph and insert SWAPs (SABRE)
Render graph (frame graph)A renderer on an explicit GPU API runs dozens of passes per frame built by independent modules. Someone must order them, place their short-lived images and buffers in memory, and synchronize every read after write.
schema migration diff · plannedDiff the desired schema against the live one into ordered, safe DDL steps, flagging destructive and locking changes
schema migration versioning · plannedVersioned forward migrations for local and server databases; expand-contract changes
Static timing analysisA team has a network of steps with known min and max durations and a deadline, and must find without simulating every case which steps cause a miss, how much slack the others have, and what an edit to one step changes.
task planning · planned
technology mapping · plannedCover logic networks with library cells or LUTs (cut enumeration, tree covering)
transitive closure index · plannedSegment/interval labelling of DAGs (commit graphs) for fast reachability queries

Systems (39):

Alternatives and related