You Might Also Like
Recursion Tree Visualizer — Free HTML CSS JS Snippet
Recursion Tree Visualizer · Animations · Plain HTML, CSS & JS · Live preview
What's included
Features
About this UI Snippet
Recursion Tree Visualizer — Animated Fibonacci Call Tree, Live Call Stack Panel & Memoization Toggle in Vanilla JS

Recursion is usually explained with a diagram of a tree that's already fully drawn, which skips the part that actually confuses people: the tree is built by a program that calls itself, descending and returning in a very specific order. This snippet makes that order visible by animating naive recursive Fibonacci as it actually executes — nodes appear as calls happen, a call-stack panel pushes and pops in real time next to the tree, and a memoize toggle lets you watch exponential blowup collapse into linear work.
Event pre-computation, same architecture as the other visualizers
buildEvents(n, memoize) runs the actual recursive fib() function to completion before any animation happens, but instead of just returning a number it pushes a timeline of events as it goes: call when a function invocation begins, memo-hit when a cached result short-circuits a would-be call, combine when a node's two children's results are summed, and return when a call finishes and should pop off the stack. This is the same separation-of-concerns pattern used by the sorting and pathfinding visualizers in this library — the recursive algorithm never touches the DOM or timing, it only narrates itself into a flat event list that gets replayed afterward.
Why naive Fibonacci is O(2^n): the branching made visible
Naive recursive fib(k) calls fib(k-1) and fib(k-2), and neither call knows anything the other computed. fib(5) calls fib(4) and fib(3); fib(4) calls fib(3) and fib(2) — notice fib(3) gets computed twice, entirely from scratch, with its own full sub-tree of calls each time. Every node in the tree spawns two more nodes until the base case (k <= 1) is hit, so the total call count roughly doubles with each increment of n — the textbook definition of O(2^n) exponential growth. Run this visualizer at n = 10 with Memoize off and watch the Calls counter: it climbs into the hundreds, and the tree visibly balloons sideways because fib(3), fib(2), and fib(1) are each independently re-derived dozens of times.
How the memoize toggle collapses the tree to O(n)
When the Memoize checkbox is on, buildEvents keeps a plain memo object keyed by the Fibonacci input k. Before making a real recursive call, fib() checks memo[k] !== undefined; if the value is already cached, it pushes a memo-hit event instead of a full call event and returns immediately without recursing further. On screen, memo-hit nodes render grey and semi-transparent, connected to their parent but visually distinct from an actively-computed orange node — you can watch entire sub-trees that would normally re-expand get reduced to a single dimmed leaf. Because every distinct k value from 0 to n is computed at most once when memoized, the total call count grows linearly with n instead of exponentially — the Calls and Skipped (memo) counters made this concrete: run fib(10) twice, once with memoize off and once on, and compare the final Calls numbers directly.
The call stack panel: pushing and popping in sync with recursion
The .stack-list panel uses flex-direction: column-reverse so the most recently pushed call visually sits on top, mimicking how a real call stack grows upward. Every call event appends a new .stack-item div labeled fib(k) with a small pushIn keyframe animation; every matching return event removes the most recently pushed still-present item. Because JavaScript's own function call stack is what's driving buildEvents's recursion in the first place, the push/pop order in this panel is not a simulation of a call stack, it is a direct visualization of the real one — the depth-first descent (call, call, call, base case, return, return) plays out in the exact order V8 itself would execute it.
Tree layout without a charting library
Node positions are computed with plain arithmetic in nodePos(x, depth): vertical position is depth * 56 pixels, and horizontal position starts each recursive call at its parent's x offset by a spread value that shrinks as depth increases (Math.max(1.6, 5 - depth * 0.6)), so left and right children fan out proportionally to how many levels of the tree remain — deep nodes cluster tightly, shallow nodes spread wide, which keeps a tree of a few hundred nodes from overlapping. Edges are drawn as SVG <line> elements appended to a plain <svg> overlay positioned absolutely over the node container, connecting each new node to its already-positioned parent the instant the child call event fires.
Build with AI
Build, Understand, Optimize, and Extend It With AI
Give this snippet's JavaScript to an AI assistant like Claude and ask it to trace exactly why fib(3) gets recomputed multiple times in the unmemoized tree, or how the memo object changes the shape of the recursion at each call site. Worthwhile extensions to ask for: a factorial mode to contrast single-branch versus double-branch recursion, a speed slider like the sorting visualizer's, or highlighting the exact chain of duplicate sub-calls that memoization eliminates in a different accent color.
Prompt to recreate it
Copy this into your AI assistant of choice to build the effect from scratch, or as a jumping-off point for your own variant:
Build an animated recursion tree visualizer for naive recursive Fibonacci in plain HTML, CSS, and JavaScript, no libraries or frameworks.
Requirements:
- Accept a number n from the user and run a real recursive fib(n) function (calling fib(n-1) and fib(n-2)) that narrates itself into a flat timeline of events (call started, call returned, values combined) rather than mixing animation code into the recursive function.
- Animate tree nodes appearing one at a time as calls happen, positioned by recursion depth (vertical) and a horizontal offset that fans out from the parent and shrinks at deeper levels to avoid overlap, connected to their parent by a line drawn the instant the node appears.
- Show a call-stack panel next to the tree that pushes a new entry when a call starts and pops it when that call returns, so the panel's contents always reflect the real, currently-active call chain — not a simplified approximation.
- Add a "Memoize" checkbox that, when enabled, caches each computed Fibonacci value by its input and, on a repeat request for an already-cached value, skips the real recursive call and instead renders a visually distinct (dimmed/greyed) node showing the cache hit.
- Track and display a live counter of total real calls made, and a separate counter of calls skipped due to memoization, so a user can directly compare the counts between memoized and unmemoized runs of the same n.
- Use plain async/await with a small delay helper to pace the animation, keeping the recursive algorithm itself free of any timing or DOM code.Want to tighten it up first? Run this prompt through the AI Prompt Studio to score it across 8 quality dimensions, catch anti-patterns, and tune the wording for Claude, ChatGPT, or Gemini before you paste it in.
Step by step
How to Use
- 1Set n for fib(n) using the number inputChoose a value from 1 to 10. Larger values without memoization produce a dramatically bigger tree, so start around 6-7 to see clean branching before trying larger numbers.
- 2Click Run with Memoize off firstWatch orange "active" nodes appear one at a time as each recursive call fires, tracing a depth-first path down the left branch before backtracking to the right.
- 3Watch the call stack panel push and popEach call adds a fib(k) entry to the top of the stack list; each return removes it, mirroring exactly what the JavaScript engine's real call stack is doing underneath.
- 4Note the final Calls counter, then toggle Memoize onRe-run the same n value with Memoize checked. Repeated sub-calls now render as dimmed grey "memo-hit" nodes instead of full orange call sequences.
- 5Compare the two Calls counts side by sideThe unmemoized run grows roughly exponentially with n; the memoized run grows roughly linearly, since every distinct fib(k) value is computed only once and reused.
- 6Try increasing n to 9 or 10 in both modesWithout memoization, the tree visibly balloons and the run takes noticeably longer to finish animating. With memoization, the extra nodes are almost entirely grey and the run finishes just as fast.
Real-world uses
Common Use Cases
Got questions?
Frequently Asked Questions
Yes. Move buildEvents() into a utils function — it is pure and takes n/memoize as arguments, returning a plain array. In React, run the playback loop (the async play() function) inside a useEffect or a click handler, and store any pending setTimeout id in a ref so you can clear it in the cleanup function if the component unmounts mid-animation. In Vue, call play() from a method and guard state updates after each await with a local "cancelled" flag set in onUnmounted. In Angular, do the same with an isDestroyed flag checked after every await and set true in ngOnDestroy. Because the timeline is just await-delayed DOM updates rather than a persistent interval, the main cleanup concern is stopping in-flight awaits from touching removed DOM nodes, not clearing a running timer.
Every call to fib(k) where k > 1 makes two further recursive calls, fib(k-1) and fib(k-2), and neither call is aware of what the other already computed. Because the two subtrees overlap heavily (fib(k-2) is recomputed as part of both fib(k-1) and fib(k) itself, and so on down the tree), the total number of calls roughly doubles with each increment to n, giving the same growth shape as 2^n. This snippet's Calls counter shows that growth directly: fib(10) without memoization takes roughly 15x more calls than fib(6).
With a memo cache keyed by the input value, each distinct fib(k) for k from 0 to n is computed at most once — every subsequent request for that same k is answered instantly from the cache instead of re-recursing. Since there are only n+1 distinct values to ever compute, and each one does O(1) work beyond its (now-skipped) recursive calls, total work becomes linear in n. This snippet visualizes that exact mechanism: memo-hit events replace what would otherwise be entire re-expanded subtrees.
The horizontal spread passed to each recursive call is computed as Math.max(1.6, 5 - depth * 0.6), so it shrinks as depth increases. This is purely a layout choice to prevent sibling subtrees from overlapping — since deeper levels have exponentially more nodes competing for the same width, giving each one a smaller horizontal offset keeps the whole tree readable instead of nodes stacking on top of each other.
Yes. Replace the fib() function inside buildEvents with any other recursive function, as long as you push the same four event types (call, memo-hit if applicable, combine, return) at the equivalent points in your logic. Factorial is simpler (single recursive branch, so no fan-out, just a straight vertical line of nodes) and a good first modification; tree traversals or the merge step of merge sort are good next steps since they naturally produce two-child branching like Fibonacci does.