webdevpuneet.com

Recursion Tree Visualizer — Free HTML CSS JS Snippet

Recursion Tree Visualizer · Visualizers · Plain HTML, CSS & JS · Live preview

CategoryVisualizers

Copy & paste

Source Code

<div class="wrap">
  <div class="toolbar">
    <div class="field">
      <label class="label">fib(n)</label>
      <input type="number" id="n-input" value="6" min="1" max="10" />
    </div>
    <button class="btn btn-primary" id="btn-run">Run</button>
    <label class="memo-toggle">
      <input type="checkbox" id="memo-check" />
      <span>Memoize</span>
    </label>
  </div>
  <div class="main-row">
    <div class="tree-panel">
      <svg id="tree-svg"></svg>
      <div class="tree-nodes" id="tree-nodes"></div>
    </div>
    <div class="stack-panel">
      <div class="stack-title">Call Stack</div>
      <div class="stack-list" id="stack-list"></div>
    </div>
  </div>
  <div class="stats-row">
    <div class="stat"><span class="stat-label">Calls</span><span class="stat-val" id="stat-calls">0</span></div>
    <div class="stat"><span class="stat-label">Skipped (memo)</span><span class="stat-val" id="stat-skipped">0</span></div>
  </div>
</div>

Extend with AI

Take This Snippet Further With AI

The AI prompt for this one is for Patreon supporters ($3 a month, 7 days free), along with a site with no ads. Supporters enter the passcode once and it is remembered in this browser.

· Become a supporter

What's included

Features

Naive recursive Fibonacci implemented exactly as textbook pseudocode describes it, calling fib(k-1) and fib(k-2) independently
Recursion narrates itself into a flat event timeline (call, memo-hit, combine, return) before any animation plays
Call stack panel is a direct visualization of the real JS call order, not a simulated approximation
Memoize toggle adds a plain object cache keyed by input value, rendering cache hits as dimmed grey nodes
Tree layout computed with pure arithmetic (depth-based Y, shrinking spread-based X) — no charting library
SVG line edges connect each new node to its parent the instant the call event fires
Live Calls and Skipped (memo) counters make the O(2^n) versus O(n) difference a comparable number
Async/await pacing with distinct delays for calls, memo-hits, and returns keeps the animation readable

About this UI Snippet

Recursion Tree Visualizer — Animated Fibonacci Call Tree, Live Call Stack Panel & Memoization Toggle in Vanilla JS

Screenshot of the Recursion Tree Visualizer snippet rendered live

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.

Step by step

How to Use

  1. 1
    Set 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.
  2. 2
    Click 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.
  3. 3
    Watch 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.
  4. 4
    Note 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.
  5. 5
    Compare 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.
  6. 6
    Try 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

Teaching recursion and Big-O complexity concretely
Run the same fib(n) with memoization off and on and compare the Calls counter directly — turns an abstract O(2^n) vs O(n) claim into two comparable numbers from the same demo. Pair with the sorting algorithm visualizer for a fuller algorithms teaching sequence.
Interview preparation for dynamic programming questions
Memoization and "top-down DP" are extremely common interview topics, and this visualizer shows exactly which sub-calls get skipped and why, building the intuition needed to explain the optimization out loud, not just implement it.
Debugging aid for understanding a real recursive function
Adapt the event-push pattern to instrument your own recursive function during development, temporarily logging call/return events to understand an unexpectedly slow or deeply nested recursion before optimizing it.
Interactive demo for a computer science course or blog
Embed as a live, adjustable demo inside an article on recursion or dynamic programming, letting readers change n and the memoize toggle themselves rather than reading a static tree diagram. Self-contained, no build step, fits any UI snippets gallery.
Onboarding material for junior engineers learning call stacks
Use the synced tree-plus-call-stack view to explain what "the call stack" concretely means during onboarding or mentoring — watching push/pop happen alongside the tree makes stack overflow errors and deep recursion costs easier to reason about.

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.