← Back to Blog
EngineeringDSANext.jsSystem DesignFrontend

How I Built AlgoLens — Interactive Algorithm Visualizer from Scratch

SY
Sumit Yadav
May 31, 20267 min read

Why I Built This

I've been preparing for Staff Engineer interviews over the past few months. One thing became clear early on — reading about algorithms and actually understanding them are two very different things.

When I studied Quick Sort, I could follow the logic. But I couldn't see the partition happening, couldn't feel the recursion splitting the array, couldn't intuitively grasp why the pivot choice matters. Every tutorial showed me pseudocode. Nobody showed me the algorithm thinking.

So I built AlgoLens.

Try it live → algolens-cyan.vercel.app


What AlgoLens Is

AlgoLens is an interactive algorithm and system design visualizer — 25+ step-by-step animated labs covering:

  • Sorting — Quick Sort, Merge Sort, Heap Sort, Radix Sort, and more
  • Searching — Binary Search, Two Pointer, Sliding Window, Interpolation Search
  • Graphs — BFS, DFS, Dijkstra's Algorithm
  • Trees — BST operations, all traversals, Trie
  • Dynamic Programming — Fibonacci (memo vs tabulation), LCS with 2D table fill
  • System Design — LRU Cache, Consistent Hashing ring visualization

Every visualizer shows you:

  • The array state at each step with colour-coded bar states
  • The active pseudocode line synced to the current operation
  • A plain-English explanation of what's happening and why
  • Time/space complexity at a glance

The Architecture Decision That Defined Everything

The most important decision I made: no external animation libraries.

No Framer Motion. No Three.js. No D3. No GSAP.

Everything — every bar animation, every colour transition, every step interpolation — is vanilla CSS and React state.

Here's why this mattered:

Control. Animation libraries make common animations easy. But a sorting visualizer needs unusual animations — bars swapping positions mid-sort, pivot bars highlighted differently from compare bars, recursive sub-arrays shown as highlighted ranges. Every library would have gotten in the way more than it helped.

Performance. With 100+ bars animating simultaneously during Radix Sort, you need tight control over what gets repainted and when. Vanilla CSS transitions on individual bar heights are extremely efficient — the browser's compositor handles them off the main thread.

Learning. The point of this project was deep understanding. Writing the animations from scratch forced me to understand exactly what each algorithm is doing at every step.


The Core Abstraction — AnimationStep

The cleanest architectural decision was a universal AnimationStep type that every algorithm must produce:

interface AnimationStep {
  values: number[]; // current array state
  compareIndices: number[]; // bars being compared (blue)
  swapIndices: number[]; // bars being swapped (red)
  sortedIndices: number[]; // confirmed sorted (green)
  pivotIndex: number | null; // pivot bar (yellow)
  activeRange: [number, number] | null; // highlighted subarray
  codeLine: number; // synced pseudocode line
  title: string; // step title
  explanation: string; // plain-English narration
}

🎬 See it live — rather than a static diagram, open AlgoLens and watch Quick Sort partition in real time.

This is the contract. Every algorithm exports one function:

function createQuickSortSteps(values: number[]): AnimationStep[];

The visualizer shell (LessonLab) knows nothing about Quick Sort or Merge Sort — it just consumes steps and drives playback. Adding a new algorithm is three files:

  1. src/lib/algorithms/youralgo.ts — the step generator
  2. Register it in src/lib/lessons.ts
  3. Create a one-line route page

This is the same separation of concerns principle I apply to production systems — decouple the data model from the presentation layer. Here, the algorithm logic is completely isolated from the UI logic.


How Quick Sort Generates Steps

Here's a simplified version of how Quick Sort produces its animation steps:

function createQuickSortSteps(values: number[]): AnimationStep[] {
  const steps: AnimationStep[] = [];
  const arr = [...values];

  function partition(low: number, high: number): number {
    const pivot = arr[high];
    let i = low - 1;

    for (let j = low; j < high; j++) {
      // Record compare step
      steps.push({
        values: [...arr],
        compareIndices: [j, high],
        swapIndices: [],
        sortedIndices: [],
        pivotIndex: high,
        activeRange: [low, high],
        codeLine: 4,
        title: "Comparing elements",
        explanation: `Comparing arr[${j}]=${arr[j]} with pivot=${pivot}`,
      });

      if (arr[j] <= pivot) {
        i++;
        [arr[i], arr[j]] = [arr[j], arr[i]];

        // Record swap step
        steps.push({
          values: [...arr],
          compareIndices: [],
          swapIndices: [i, j],
          sortedIndices: [],
          pivotIndex: high,
          activeRange: [low, high],
          codeLine: 6,
          title: "Swapping elements",
          explanation: `arr[${j}] ≤ pivot, swapping arr[${i}] and arr[${j}]`,
        });
      }
    }

    // Place pivot in final position
    [arr[i + 1], arr[high]] = [arr[high], arr[i + 1]];
    return i + 1;
  }

  function quickSort(low: number, high: number): void {
    if (low < high) {
      const pi = partition(low, high);
      quickSort(low, pi - 1);
      quickSort(pi + 1, high);
    }
  }

  quickSort(0, arr.length - 1);
  return steps;
}

The key insight: run the real algorithm, but snapshot state at every meaningful operation. The algorithm runs to completion immediately — generating a flat array of steps. Playback is just incrementing an index through that array. No timers during step generation, no async logic, no complexity. Just a pure function.


System Design Visualizers — The Differentiator

Most DSA visualizers stop at sorting and searching. AlgoLens goes further with system design flows — and this is where it gets interesting.

LRU Cache

The LRU Cache visualizer shows the doubly-linked list and HashMap side by side. Every get and put operation animates both structures simultaneously — you see exactly why DLL + HashMap gives you O(1) eviction.

interface LRUStep {
  cacheMap: Map<number, number>; // key → value
  order: number[]; // front = most recent
  operation: "get" | "put" | "evict";
  accessedKey: number;
  explanation: string;
}

Consistent Hashing

The Consistent Hashing visualizer renders the hash ring with servers placed at their hash positions. When you add or remove a server, it animates which keys get remapped — and you can see how virtual nodes dramatically improve load distribution.

This directly maps to concepts from my Consistent Hashing blog post. Seeing the ring move makes the theory click in a way reading about it never did.


The Playback Engine

The LessonLab component drives all playback with a simple state machine:

const [currentStep, setCurrentStep] = useState(0);
const [isPlaying, setIsPlaying] = useState(false);
const [speed, setSpeed] = useState(600); // ms per step

useEffect(() => {
  if (!isPlaying) return;
  if (currentStep >= steps.length - 1) {
    setIsPlaying(false);
    return;
  }

  const timer = setTimeout(() => {
    setCurrentStep((prev) => prev + 1);
  }, speed);

  return () => clearTimeout(timer);
}, [isPlaying, currentStep, speed]);

Clean, predictable, easy to test. The speed slider maps to the speed state — slower speed means longer timeout, giving more time to understand each step.


What I Learned Building This

1. Pure functions are the foundation of testable UI

Every step generator is a pure function — same input always produces the same steps. This made debugging straightforward: if a sorting animation looks wrong, run the step generator in isolation and inspect the output.

2. CSS transitions > JavaScript animations for performance

Animating bar heights with CSS transition: height 0.3s ease is dramatically more performant than JavaScript-driven animations. The browser compositor handles it on a separate thread — the main thread stays free for React re-renders.

3. The explanation panel is as important as the animation

Early versions had the animation without the plain-English narration. Users could see what was happening but not why. Adding explanation to every step transformed comprehension. Visual + verbal together is how understanding sticks.

4. System design visualizers are underserved

Every DSA visualizer does sorting and trees. Almost none touch consistent hashing, LRU cache, or load balancing. These are the concepts that separate Senior from Staff Engineer interviews — and they're the most satisfying to see animated.


What's Next

AlgoLens is actively expanding. On the roadmap:

  • Interview Mode — timed prompts with hints, complexity checks, and scoring
  • AVL Trees — self-balancing with rotation animations
  • A* Search — heuristic pathfinding on a grid
  • Coin Change & Edit Distance — completing the DP section
  • Message Queue, Rate Limiter, CDN — more system design flows
  • Comparison View — run two algorithms on the same input simultaneously

If you find AlgoLens useful, the repo is open source — contributions welcome.

GitHub → github.com/ysumit99/algolens

Try it live → algolens-cyan.vercel.app


Building AlgoLens reminded me why I became an engineer. Not to write code — but to build things that make other people understand things faster, more deeply, more intuitively. That's the goal. Every algorithm, animated.

← More ArticlesConnect on LinkedIn →