ALGORITHMS · 08  /  DFS & TOPOLOGICAL SORT

Build Order.

Eight tasks, each waiting on the ones before it. Schedule them by hand and feel the constraints bite — then let a depth-first search dive to the bottom of every dependency and surface a valid build order every time. Then add one bad edge and watch it refuse.

THE GIST · 20 SECONDS

A topological sort lays the vertices of a DAG in a line so every arrow points forward — every task after its prerequisites. The trick: run DFS, and the moment a node finishes (all its descendants explored), push it to a list. Reverse that list and you have a valid order. It runs in O(V + E) — and a cycle is exactly what makes it impossible.

  • DAGdirected graph, no cycles
  • In-degreearrows pointing into a node
  • Finish timewhen DFS leaves a node
  • Back edgeedge to an in-progress node = cycle

Order the graph yourself  →  Let DFS reverse its finishes  →  Add a cycle, watch it refuse

BUILD ORDER
— nothing scheduled yet —
ready to schedule on the stack (visiting) finished / placed in a cycle

An arrow a → b means a must be built before b. DFS dives to the deepest dependency first, so a node only finishes after everything it points to — which is exactly why reversing the finish order gives a legal build order.

UNDER THE HOOD

What you just played, written down

You scheduled it by hand, watched DFS dive and finish nodes bottom-up, then saw a back edge expose a cycle. Here's the same thing as code.

How the Foreman thinks — five steps

  1. Pick any unvisited node. Mark it gray — it's now on the recursion stack.
  2. Recurse into its successors. For each edge out of it, dive into that node first if it hasn't been visited.
  3. Finish the node. Once every successor is done, mark it black and push it onto a list.
  4. Repeat from any node still unvisited, until all are black.
  5. Reverse the list. The reversed finish order is a valid topological order.
WHY REVERSE-POST-ORDER WORKS

For any edge u → v, DFS finishes v before u (it explored v while still inside u). So u sits later in the finish list, and after reversing, u sits earlier — exactly the order topological sort requires. Catch a gray node mid-recursion and you've found a cycle: no order exists.

The algorithm

WHITE, GRAY, BLACK = 0, 1, 2

function topoSort(graph):
    color = {v: WHITE for v in graph}
    order = []                       # finish order
    for s in graph:
        if color[s] == WHITE:
            dfs(s)
    return reversed(order)

function dfs(u):
    color[u] = GRAY                  # on the stack
    for v in neighbours(u):
        if color[v] == GRAY:
            raise CycleError         # back edge!
        if color[v] == WHITE:
            dfs(v)
    color[u] = BLACK
    order.append(u)                  # finished
TIMEO(V + E)every node + edge once
SPACEO(V)colors + recursion stack

Solved by hand — the run for the graph above

DFS finishes nodes deepest-first; reverse that and every arrow points forward.

↔ DFS vs Kahn's algorithm

The DFS way reverses finishing times. Kahn's way is BFS-flavoured: repeatedly emit any node with in-degree 0, then remove its out-edges. Both are O(V + E); Kahn's also surfaces the cycle as "nodes that never reach in-degree 0".

↔ Why a cycle breaks it

In a cycle a → b → … → a, every node waits on another in the loop, so none can go first. A topological order exists iff the graph is acyclic — that's what the D and A in DAG guarantee.

★ Where it's used

Build systems (make, Bazel) picking compile order, package managers, task schedulers, spreadsheet recalculation, course prerequisites, and database migration ordering.

RUN IT YOURSELF

The same algorithm, in Python & TypeScript

Both tabs are the identical algorithm (Kahn's), running for real in your browser — Python through WebAssembly, TypeScript transpiled on the fly. No server. Switch tabs to compare the languages line for line, read the numbered comments, edit anything, and hit Run (or ⌘/Ctrl + Enter). Each language downloads its runtime once on first run.

HOW TO READ THE CODE — 4 IDEAS
  1. in-degree = how many prerequisites a task is still waiting on. The comments mark this step 1.
  2. Any task at in-degree 0 has nothing left to wait for, so it's ready to build now (step 2).
  3. Building a task frees its dependents: drop each of their counters by one; when a counter hits 0, that task becomes ready too (steps 3–5).
  4. If some tasks never reach 0, they're stuck waiting on each other — a cycle, so no order exists (step 6).
CPython · WebAssembly
QUICK CHECK

Did it stick?

FAQ

Topological sort, answered

What is a topological sort?

A linear ordering of a DAG's vertices so that for every edge u → v, u comes before v. It linearizes "do this before that" constraints — and exists if and only if the graph has no cycle.

How does DFS produce a topological order?

Run DFS; each time a node finishes (all descendants explored), push it to a list. Reverse the list. Because a node always finishes after everything it points to, the reversed order puts every dependency first.

What is the time complexity?

O(V + E) for both the DFS method and Kahn's algorithm — each vertex and edge is touched once. Space is O(V) for colors plus the recursion stack or the queue.

How does it detect a cycle?

Mark a node gray while it's on the recursion stack, black when it finishes. An edge to a gray node is a back edge — it points to an ancestor still in progress — which means a cycle, so no order exists.

Is the topological order unique?

Usually not. Two nodes with no path between them can go in either order, so most DAGs have many valid orders. It's unique only when the DAG is a single chain.

Where is topological sort used?

Build systems and compilers (compile order), package managers, task schedulers, spreadsheet recalculation, course prerequisites, dependency injection, and ordering database migrations.

RUN CARD

Next → keep the same DFS machinery but track which nodes are still on the stack across the whole graph, and you can find strongly connected components.

Finished this one? 0 / 8 Algorithms done

More Algorithms