All posts

Learning

The animation that lied

An animation can be correct in every pixel and still teach the wrong thing — by faking a state the code never reaches, or by selling a limit of the canvas as a limit of the algorithm. The worst bugs I've shipped weren't crashes. They were lies a learner had no reason to doubt.

June 8, 20266 min read

The hash table told me the bucket had overflowed. I'd been dropping numbers into a separate-chaining table, watching them collide and pile up in the same bucket, and on the seventh one the chain went red and refused it. Overflow. Except a hash bucket doesn't overflow — the chain can grow as long as it likes, which is the entire point of separate chaining. It went red at seven because seven nodes is what fit before the chain ran off the bottom of the canvas. That's a fact about my layout code. The animation had dressed it up as a fact about the data structure, and shipped it as a lesson.

Nobody would have filed that as a bug. Nothing crashed. The animation was smooth and the colors were right. It just quietly taught that hash chains have a maximum length, which they don't, and a learner watching it had no reason to doubt a single frame.

A wrong picture is worse than no picture

This is the risk the project kept reminding me of, because here the animation is the explanation. There's no separate authority standing next to it — no lecturer you can interrupt to ask "wait, is that really how it works?" The picture is the source of truth. So when the picture is wrong, there's nothing to check it against, and someone walks away having confidently learned something false. They'd have been better served by a paragraph of plain text.

And the smoother the animation, the worse it gets. A janky, stuttering visualization keeps you a little wary; you can see the seams, so you don't hand it your full trust. A clean one earns that trust completely, which is exactly what makes one false frame inside it so expensive. Polish is credibility. Spend it on a lie and the lie inherits all of it. So the failures I've come to fear aren't the ones that throw — they're the ones that render perfectly and teach the wrong thing.

The lie in the caption

The hash chain was the first kind I kept running into: a limit of the drawing, captioned as a limit of the algorithm. The canvas couldn't hold an eighth node, so the chain capped, and the nearest word was "overflow."

The fix wasn't a bigger canvas. It was to stop lying about why the cap is there. The cap still exists — six nodes is genuinely all that fits and stays legible — but the message now says, in those words, that this would exceed the visualization depth and the real structure has no such limit. Same cap, honest caption. A learner can be told "we can only draw six of these at once"; what they can't be told is that the seventh one can't exist. There's a comment sitting in that file now, several lines of it, reminding whoever touches it next that the number is about pixels and never about hashing — because the slip is so easy to make twice.

The lie in the highlight

The second kind is quieter, and I still find it. Every beat of an animation lights up the lines of code it corresponds to, and the temptation is to map a whole function to the whole operation and move on. But a lookup that finds nothing runs different lines than a lookup that hits, and a probe that fails takes a different branch than one that succeeds. Map them both to the same highlight and you get a beat where the search comes up empty while the line that returns the match glows anyway. Smooth. Wrong. Teaching that the failing path executes its own success branch.

These came out of going lesson by lesson and, more often, out of review passes — sometimes another model reading a lesson back to me and pointing at the frame that didn't add up. A failed probe, a heap comparison, a scheduler dequeue: each one had a highlight covering code the step never ran. The repairs kept having the same shape — the branch that actually fired gets its own beat and its own lines, instead of one label pretending to cover two paths. There's a test now that walks every trace and fails if a beat lights up nothing at all; it can't catch a beat lighting the wrong line, which is still on me to see, but it draws a hard floor under the silent version.

Not every mismatch is a lie

The overcorrection is to decide that any gap between the animation and the code is dishonest. It isn't, and ruling that way would make every lesson worse. Inserting at the head of a linked list is one conceptual move, but the animation breaks it into beats: stage the new node, point its arrow at the old head, then swing the head pointer over. Three beats for what reads as one line. That's not padding the truth — it's the truth at the resolution where it can actually be learned, because the order of those pointer moves is the whole reason people get linked lists wrong.

The check I use now is this: take as many beats as you want to show a real transition, but never show a state the code doesn't pass through. Slowing the truth down, holding on it, expanding it — all fine. Animating a tidy halfway state that the real execution skips, because it makes a smoother tween, is where it tips into a lie. Rhythm doesn't license invention.

The order the mistakes kept teaching

I didn't start with a checklist for any of this. It accumulated. After enough lessons and enough reviews, what I noticed was that whenever two things I wanted were in conflict, they sorted themselves into the same order without my deciding it: get it right first; keep the picture, the code, and the words all describing the same instant; then make it followable; then make it look consistent with the other lessons; and make it pretty last. A choppier frame that's honest beats a gorgeous one that fakes an in-between, every time — not because it sounds good, but because it's the thing I actually reach for when I catch myself wanting to smooth something over.

The plainest example is the one that embarrasses me most. The optimized bubble sort can stop early when a pass makes no swaps — that early exit is the entire reason the optimization exists. And the final beat cheerfully announced "all passes complete," even on the runs that stopped early, contradicting the exact property the lesson was built to show. It took a review to catch it. Pretty, smooth, and quietly undermining its own point.

If I'm going to let the animation carry the explanation, that cuts both ways. When it's right it can teach things a paragraph struggles to carry; when it's wrong it can lie more convincingly than a paragraph ever could. Beauty isn't the goal. Beauty is the budget, and the only honest place to spend it is on something true.

See it: open the hash table with chaining and keep inserting into one bucket — past six, the message names the limit as the drawing's, not the data structure's. Then step through a linked-list insertion and watch one line of code unfold into the separate pointer moves it really makes, in the order it makes them.