I spent the last few months building visual walkthroughs for data structures and algorithms. Not diagrams — step-by-step animations where you watch the pointers move, the stack grow, and the visited set fill in.
Somewhere in the middle of it I discovered the animations were lying to me. Here is what went wrong and what I did about it.
The problem with hand-written animation steps
My first version hardcoded every animation frame. A binary search page had a list of steps like this:
steps: [
{ lo: 0, hi: 9, mid: 4, note: "Check the middle" },
{ lo: 5, hi: 9, mid: 7, note: "Target is larger, go right" },
// ...
]
This works right up until you change the input array. Then every frame after the first is quietly wrong, and nothing tells you. The animation still plays. It still looks convincing. It just teaches the wrong thing.
I found this the embarrassing way: a tree traversal page was animating the correct-looking sequence for a tree that no longer matched the diagram beside it. The picture said one thing, the animation said another, and both were rendered from different sources of truth.
Running the real algorithm instead
The fix was to stop writing the frames and start recording them. Each page now runs the actual algorithm and logs a snapshot at every meaningful state change:
function trace(arr, target) {
const steps = [];
let lo = 0, hi = arr.length - 1;
while (lo <= hi) {
const mid = (lo + hi) >> 1;
steps.push({ lo, hi, mid, note: `Compare ${arr[mid]} against ${target}` });
if (arr[mid] === target) return steps;
if (arr[mid] < target) lo = mid + 1;
else hi = mid - 1;
}
return steps;
}
The animation is now a byproduct of the algorithm, not a parallel description of it. Change the input and every frame updates. Break the algorithm and the animation breaks visibly, in the same way.
The part that actually mattered
The real win was that this made the animations testable. Once the steps come from running code, you can assert on the last one:
const steps = trace([1, 3, 5, 7, 9, 11], 7);
assert.equal(steps.at(-1).mid, 3);
I now have 1,096 of these assertions across every animation on the site. They run on every build. The suite has caught things I would never have noticed by watching:
- A heap animation that showed the sift-down but stopped one swap early, so the final heap it displayed was not actually a valid heap.
- Ten separate pages animating a linked list to explain a tree, because the generic fallback visual was easier than writing the right one.
- Several tracers truncated at exactly seven steps — a number that had crept in as a default and then silently became the ceiling for operations that needed fourteen.
That last one is my favourite failure. Every one of those pages looked finished.
What I would tell my earlier self
If a visual explanation is not generated by the thing it explains, it will drift. Documentation drifts from code for exactly the same reason, and we have long since accepted that generated docs beat hand-written ones. Animations are no different — they are just documentation that moves.
Test the output, not the rendering. I do not screenshot-test the SVG. I assert on the final state of the trace. That catches the errors that matter (wrong algorithm) and ignores the ones that do not (a node moved three pixels).
A convincing wrong explanation is worse than no explanation. A learner who reads a static wrong sentence often catches it. A learner watching a smooth animation assumes the machine knows better than they do. Getting this right felt less like polish and more like an obligation.
The result
The site is SolveLog — worked LeetCode problems and data structure lessons, each with its own generated animation. A few where the tracer approach earns its keep:
- Binary search — watch the search window actually collapse
- AVL trees — the rebalance rotations are the whole point, and they are impossible to follow as static pictures
- Backtracking — the call stack panel beside the tree is what made this click for me
It is free and there is no signup. I built it to learn this material properly myself, and the testing discipline above is the only reason I now trust it.
Happy to answer questions about the tracer architecture in the comments.
Top comments (0)