JavaScript Memoization

Memoization is a technique that stores the result of a function call so the same calculation does not run again for the same input. When you call a memoized function with an argument it has seen before, it returns the stored result instantly instead of recalculating.

The Real-World Analogy

Imagine a student solving math problems. The first time they see "12 × 13", they work it out: 156. They write that answer in the margin. The next time "12 × 13" appears, they just read the margin — no calculation needed. Memoization is exactly that notepad in the margin.

Diagram: Memoization Flow

First call: square(6)
  ┌───────────────────────────┐
  │ Check cache: 6 → not found│
  │ Calculate: 6 × 6 = 36     │
  │ Store: cache[6] = 36      │
  │ Return: 36                │
  └───────────────────────────┘

Second call: square(6)
  ┌──────────────────────────┐
  │ Check cache: 6 → found!  │
  │ Return: 36 (no work done)│
  └──────────────────────────┘

Without Memoization

Every call recalculates from scratch, even when the answer is the same.

function square(n) {
  console.log("Calculating...");
  return n * n;
}

console.log(square(5)); // Calculating... → 25
console.log(square(5)); // Calculating... → 25 (again!)
console.log(square(5)); // Calculating... → 25 (again!)

With Memoization

function memoize(fn) {
  let cache = {};

  return function(n) {
    if (cache[n] !== undefined) {
      console.log("From cache!");
      return cache[n];
    }
    console.log("Calculating...");
    cache[n] = fn(n);
    return cache[n];
  };
}

function square(n) {
  return n * n;
}

let memoSquare = memoize(square);

console.log(memoSquare(5)); // Calculating... → 25
console.log(memoSquare(5)); // From cache!   → 25
console.log(memoSquare(5)); // From cache!   → 25
console.log(memoSquare(7)); // Calculating... → 49

Diagram: Cache Object Growing Over Time

After memoSquare(5):   cache = { 5: 25 }
After memoSquare(5):   cache = { 5: 25 }  (no change)
After memoSquare(7):   cache = { 5: 25, 7: 49 }
After memoSquare(10):  cache = { 5: 25, 7: 49, 10: 100 }

Why Memoization Matters: Fibonacci Example

The Fibonacci sequence is a classic example. Each number is the sum of the previous two: 0, 1, 1, 2, 3, 5, 8, 13…

Without Memoization (Slow)

function fib(n) {
  if (n <= 1) return n;
  return fib(n - 1) + fib(n - 2);
}

console.log(fib(40)); // Takes a long time!

Diagram: Repeated Work Without Memo

fib(5)
├── fib(4)
│   ├── fib(3)
│   │   ├── fib(2) ← calculated
│   │   └── fib(1) ← calculated
│   └── fib(2) ← calculated AGAIN (wasted work)
└── fib(3) ← calculated AGAIN (wasted work)
    ├── fib(2) ← calculated AGAIN
    └── fib(1) ← calculated AGAIN

With Memoization (Fast)

function memoFib() {
  let cache = {};

  return function fib(n) {
    if (n in cache) return cache[n];
    if (n <= 1) return n;

    cache[n] = fib(n - 1) + fib(n - 2);
    return cache[n];
  };
}

let fib = memoFib();

console.log(fib(40)); // Instant!
console.log(fib(50)); // Instant!

Diagram: Each fib Value Calculated Once

fib(5) calculated → stored in cache
fib(4) calculated → stored in cache
fib(3) calculated → stored in cache
fib(2) calculated → stored in cache

Next time fib(3) is needed → just read cache[3], no calculation

Memoization With Multiple Arguments

When a function takes multiple arguments, use a string key that combines them.

function memoize(fn) {
  let cache = {};

  return function(...args) {
    let key = JSON.stringify(args);  // "["3","5"]" as key

    if (cache[key] !== undefined) {
      return cache[key];
    }

    cache[key] = fn(...args);
    return cache[key];
  };
}

function add(a, b) {
  return a + b;
}

let memoAdd = memoize(add);

console.log(memoAdd(3, 5));  // calculates → 8
console.log(memoAdd(3, 5));  // from cache → 8
console.log(memoAdd(2, 7));  // calculates → 9

Real-World Use: API Response Caching

Memoization prevents duplicate network calls for the same data.

function memoize(fn) {
  let cache = {};
  return function(key) {
    if (cache[key]) return Promise.resolve(cache[key]);
    return fn(key).then(data => {
      cache[key] = data;
      return data;
    });
  };
}

async function fetchUser(id) {
  let response = await fetch("/api/users/" + id);
  return response.json();
}

let memoFetchUser = memoize(fetchUser);

memoFetchUser(1); // network call
memoFetchUser(1); // returns cached result — no network call

Memoization vs Caching

AspectMemoizationGeneral Caching
ScopePer function, inside the functionAnywhere — database, HTTP, disk
ExpiryLasts until function is replacedCan expire with TTL
Best forPure functions with expensive computationNetwork calls, sessions, page data

When Not to Use Memoization

  • Functions with side effects (like writing to a database) — the effect will not repeat on cache hits.
  • Functions where the output changes over time for the same input.
  • Functions called with thousands of unique arguments — the cache grows very large.
  • Cheap functions where the cache lookup overhead costs more than recalculating.

Summary

Memoization stores a function's results so repeated calls with the same input return instantly without recalculating. It works by keeping a cache object keyed on the function's arguments. The Fibonacci example clearly shows how memoization can turn an exponentially slow function into a fast one. Use it for pure functions with expensive or repeated calculations — but skip it when functions have side effects or ever-changing outputs.

Leave a Comment

Your email address will not be published. Required fields are marked *