For two modules, the pattern has been the same: find ready nodes, process them, watch in-degrees drop. Small graphs where you can hold the whole structure in your head.
Now the graph has 10 nodes and a web of dependencies. Can you still do it? More importantly, can you describe the procedure you're following — scanning for zero-in-degree nodes, picking one, removing it, updating neighbors, checking for newly ready nodes? Over and over.
That procedure has a name, a formal specification, and a runtime complexity. But first, let's see if you can run it on a graph where visual intuition alone isn't quite enough.
Ten nodes, a web of dependencies, and you're the scheduler. Find the ready nodes (their in-degree badges are hidden during predictions — you'll need to trace the arrows). Process them one at a time. Watch the cascade ripple through the graph.
Around step 4 or 5, pay attention to what you're doing. You're following a pattern. Can you name the steps?
If that felt mechanical by the end — like you were just going through the motions — that's exactly the point. You weren't thinking about the algorithm anymore. You were running it. Find ready nodes, pick one, process it, update neighbors, enqueue any that become ready. The procedure became automatic because it's the only sensible thing to do in a DAG. There's no cleverness required, no optimization to discover — just a systematic sweep.
You also noticed something subtle: when multiple nodes were ready at the same time, it didn't matter which one you picked. Both orderings would be valid. That flexibility isn't a bug — it's a feature. All simultaneously-ready nodes are independent of each other. No edge connects them directly, so no ordering constraint exists between them. You can process them in any order, and the result is still a valid topological ordering.
This means the algorithm doesn't produce “the” ordering — it produces “an” ordering. The specific output depends on which ready node you pick when multiple are available. Different tie-breaking rules produce different valid orderings of the same graph. We'll come back to this when you construct two orderings for the same graph at the end of this module.
Did you notice? You've been following a pattern this whole time. Find nodes with no remaining dependencies. Process one. Update the blockers on its neighbors. Check if any neighbors became unblocked. Repeat.
That pattern has a name: Kahn's Algorithm, published by Arthur B. Kahn in 1962. You didn't learn it from a textbook — you derived it from necessity. Every step you took maps directly to a line of code.
Here's the algorithm you just ran:
The beautiful thing? This runs in O(V + E) time — one pass through every node and every edge. You visit each node exactly once (when it exits the queue) and each edge exactly once (when you decrement the neighbor's in-degree). You can't do better than looking at every element once.
And there's a bonus: if the queue empties before all nodes are processed, you've found a cycle. The remaining nodes are trapped — their in-degrees will never reach zero because they're all waiting on each other. Kahn's algorithm is both a sorter and a cycle detector, for free.
Here's a distinction that trips up a lot of people. Kahn's is sometimes called “BFS-based topological sort” because it uses a queue. But this is not breadth-first search. BFS explores a graph level by level from a starting node to find shortest paths. Kahn's processes nodes by readiness to produce an ordering. Same data structure, completely different semantics. The queue in Kahn's tracks “who has zero blockers right now,” not “who is N edges away from the source.”
We'll revisit that distinction later with a direct comparison. For now, let's see how your manual procedure maps to actual code.
Here's where abstraction meets implementation. Split screen: the graph on one side, Kahn's algorithm in TypeScript on the other. At each step of the algorithm, you'll tap the code line that corresponds to the current graph action. This isn't a passive walkthrough where you click “Next” to advance a highlighter — you choose which line fires next, mapping your intuition to specific lines of code.
When the code hits a prediction point, the in-degree badges hide. You'll predict the computed value before the code reveals it. Getting it wrong is the most instructive outcome — it reveals where your mental model diverges from the machine's execution.
in-degrees. Tap the matching code section.Every tap you made — "this action is the for loop," "this action is the queue.push" — was a connection between intuition and implementation. The code isn't doing anything you haven't already done by hand. It's just doing it in a language the computer understands.
The inDegree[neighbor]-- line is the ripple you watched in Module B. The if (inDegree[neighbor] === 0) check is the moment a node lights up as ready. The queue.shift() is you picking the next task to process. Every line of code encodes a decision you've been making for two modules.
If you got a prediction wrong — if the in-degree value surprised you — that's the most valuable moment in this entire exercise. It means the code revealed a case your mental model didn't account for. Maybe a node had more blockers than you realized, or a neighbor became ready sooner than expected. Those surprises are how mental models get refined.
So far, the queue has always had work to do. Ready nodes kept appearing. The cascade never stalled.
But what happens when the graph has a cycle? You already know cycles make ordering impossible — you proved that in Module A. But how does Kahn's algorithm detect the cycle? It doesn't scan for loops or trace paths. The detection is eerily passive.
Process the queue until something goes wrong. Then figure out what happened.
The queue starved. It ran out of ready nodes while unprocessed nodes remained — nodes whose in-degrees refused to drop to zero because they were waiting on each other in a loop. Nobody in the cycle could go first, so nobody entered the queue, so the algorithm halted early.
This is cycle detection by absence. The algorithm doesn't look for cycles. It simply notices that result.length < n when it finishes — fewer nodes were processed than exist in the graph. The missing nodes are exactly the ones trapped in cycles.
In code, the check is a single line after the main loop:
if (result.length < n) { // Cycle detected — remaining nodes are trapped}No cycle-tracing, no DFS, no visited arrays. The cycle reveals itself as a side effect of the algorithm's normal operation. That's what “cycle detection for free” means.
There's something philosophically satisfying about this. The same mechanism that produces the ordering — the in-degree cascade — is what detects the failure. If the cascade runs to completion, you get an ordering. If it stalls, you get a diagnosis. One algorithm, two outcomes, zero extra work.
This is why Kahn's algorithm is the standard choice when you need both a topological ordering and cycle detection. DFS-based topological sort (which you'll encounter in a later module) can also detect cycles, but through a different mechanism — back edges during traversal. Kahn's detection is arguably more intuitive: the algorithm simply tried to order the graph and ran out of processable nodes. The cycle isn't found so much as exposed by the algorithm's inability to proceed.
You've mapped your actions to code lines. You know which line handles the dequeue, which handles the decrement, which handles the enqueue check. Now let's see if you can keep pace with the actual execution.
On one side: the graph, where you process nodes manually by tapping ready nodes. On the other: Kahn's code, executing automatically at a steady pace. Both sides process the same graph. The code doesn't wait for you. Can you match its output step for step?
If you and the code produced different orderings — and both are valid — that's the non-uniqueness property in action. When multiple nodes are ready simultaneously, any choice is correct. The code uses a deterministic tie-breaking rule (process the smallest-indexed node first), while you used your own intuition. Both orderings respect every single dependency in the graph.
Here's the deeper lesson. The code is faster because it's mechanical — it doesn't deliberate, doesn't trace arrows with its eyes, doesn't second-guess. But you understand why each step works. You know that queue.shift() pulls a ready node because you've experienced what “ready” means at a visceral level. You know that inDegree[neighbor]-- is the ripple because you've watched neighbors unlock in real time. The code is a machine that executes. You're the one who knows the machine is correct.
That understanding is what lets you modify the algorithm for new situations. What if you needed the lexicographically smallest ordering? You'd swap the queue for a min-heap. What if you needed all valid orderings? You'd branch at every tie. The code can't make those decisions. You can, because you understand the invariant: any node with zero remaining blockers is safe to process next.
Time to address the elephant in the room. Kahn's algorithm uses a queue. BFS uses a queue. Both process graphs. Both visit nodes in waves. So what's the difference?
This comparison trips up more interview candidates than almost any other graph question. They see a queue, they see level-by-level processing, and they think “BFS.” The similarity is real but superficial — like saying a stack of plates and a call stack are the same thing because they're both stacks. The data structure is identical. What goes into it, and why, is completely different.
The shortest path from A to E has total weight 5. Did Kahn's find this shortest path?
The core difference is what the queue represents. In BFS, the queue holds nodes at increasing distances from a source — level 0, then level 1, then level 2. In Kahn's, the queue holds nodes with zero remaining blockers — nodes that are ready, regardless of how far they are from any source.
A BFS ordering tells you how far each node is from the starting point. A Kahn's ordering tells you a valid sequence for processing dependencies. These are fundamentally different questions, and the answers look different too. BFS might put node X before node Y because X is closer to the source. Kahn's might put Y before X because Y had fewer dependencies.
Same data structure. Same “dequeue, process, enqueue neighbors” loop. Completely different semantics, completely different outputs. If someone asks you “is Kahn's algorithm just BFS?” in an interview, the answer is an emphatic no — and now you can explain exactly why, with a concrete example of how the two algorithms produce different results on the same graph.
This distinction matters beyond interviews because topological ordering enables shortest-path computation on DAGs — but it doesn't perform it. You can use a topological ordering to set up a DP table for shortest paths, processing nodes in dependency order so that every predecessor's distance is finalized before you compute the current node's. But the ordering itself says nothing about distances or weights. Ordering is the foundation; DP on that ordering is the payoff. That connection is the subject of a future module.
Three final challenges. Fill in the missing code. Detect a cycle from the algorithm's output. Construct two different valid orderings for the same graph.
Q1: Fill in the blanks
You didn't learn Kahn's algorithm by memorizing pseudocode. You discovered it by processing graphs with your own hands — finding ready nodes, watching the cascade, racing the code. The algorithm was always there in your actions. Kahn just gave it a name.
The entire procedure — compute in-degrees, seed the queue, process-and-cascade, check for completeness — runs in O(V + E) and detects cycles as a free side effect. It's one of the most elegant algorithms in computer science, and you derived it from first principles.
In a future module, you'll learn what happens when you apply DFS instead of BFS to topological sorting — a completely different traversal strategy that produces the same result for a fundamentally different reason. The fact that two radically different approaches both produce valid topological orderings tells you something deep about the structure of DAGs.