Course Schedule II (LC 210): numCourses courses labelled 0..n−1 and
prerequisite pairs [a, b] meaning b must be finished before a; return
any order that finishes them all, or [] if none exists. The brief’s sentence
is the whole problem — a cycle and an impossible ordering are the same fact,
so look for the quantity you can check at the end that proves you got stuck
without having searched for a loop — and the piece draws exactly that
quantity being produced.
The board holds one disc per course, laid out left to right by how deep in the prerequisite chain it sits, each wearing an amber badge with the number of prerequisites it still waits on. Under the board, two strips: the ready queue, which holds every disc currently at zero, and the order, which fills in as discs are taken. The run is Kahn’s algorithm one event per frame. Counting the pairs puts a number on every badge; every disc at zero drops into the queue; a pop takes one out — the white ring — and emits it; then each of its outgoing edges fires in turn, white, and the disc at the far end ticks down by one. A disc that ticks to zero gets a pulse and drops into the queue in the same frame. That is the only way into the queue, and it is arithmetic, not search: nobody ever asked whether a loop exists.
The reading the piece is built for is the one where the queue runs dry
with discs still on the board. The order strip says 4 of 7 emitted · 4 < 7, so a cycle; the leftover discs go red and hatched, each still wearing a
count above zero; the edges between them go red too. The story line names
which of them form the cycle and which are merely stuck behind it, by peeling
the leftovers from the other end — which is also the answer to the follow-up
“so which courses are impossible?” that a bare [] cannot give.
Three switches.
pick decides which ready disc comes out: the one that has waited longest (a queue, the textbook BFS), the most recently readied (a stack), or the smallest label (what a heap would give you — the lexicographically smallest order). The order changes; the validity never does, and the final frame says how many valid orders the instance has (a DP over subsets, so the number is real), so “the queue was only ever doing give me any ready vertex” is something you can test by switching it.
[a,b] means a before b is the classic bug: the pair read backwards, every
arrow on the board pointing the wrong way. On a DAG the run completes, the
count says n = n, and the output is the reverse of a valid order — the
story names the first violated pair, the readout says invalid, and nothing
in the algorithm noticed. The count check proves there was no cycle; it
cannot prove the edges were built right.
return whatever came out drops the count check. On the cycle presets the
function hands back the partial order with a straight face — [0, 4, 5, 6]
for seven courses — and the readout says wrong, 3 stuck, returned anyway.
One comparison, and this is what skipping it costs.
Five presets, or type your own: up to twelve courses and forty pairs, in
LeetCode’s [[1,0],[2,0]] shape or just the numbers.
Reuse
src/unblocked.js is a framework-free ES module:
parseCount(text),parsePairs(text, n),formatPairs(pairs)— inputs out of free text, capped and deduplicated.findOrder(n, pairs)— the plain answer, as you would write it.validate(n, pairs, order)— the oracle: complete, and no pair violated.simulate(n, pairs, { pick, direction, check })— runs one instance and returnsframes(one per event —count,seed,pop,fire,done— each with a snapshot of the in-degrees, the queue, the order so far and the edges fired), plusresult,stuck,core(the stuck courses actually on a cycle),violations,orders(how many valid orders exist) andok.pickis'queue' | 'stack' | 'smallest';directionis'b-before-a' | 'a-before-b';checkis'count' | 'none'. Same trace-per-tick shape aswildcard,weave,relink,pruneandledger.cycleCore(stuck, edges)— which stuck courses are on a cycle, by peeling from the other end.countOrders(n, pairs)— the number of valid orders, DP over subsets (n ≤ 16).layout(run)— columns by longest path from a source over the edges the run used; courses that never unblock share a final column.
No rendering or timers in the module; the canvas demo is reference code.
Gotchas
- The layout follows the arrows the run believed in, so with the direction bug on the whole board mirrors — sources on the left are the courses that nothing (wrongly) depends on. That is the point: the picture is wrong in the same way the code is.
countOrdersis exponential inn; the demo capsnat twelve, where 4096 subsets is nothing. Past sixteen it returnsnullrather than hang.- The demo bundles its own copy of
unblocked.js(self-contained by contract); re-copy after editingsrc/.