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
| Aspect | Memoization | General Caching |
|---|---|---|
| Scope | Per function, inside the function | Anywhere — database, HTTP, disk |
| Expiry | Lasts until function is replaced | Can expire with TTL |
| Best for | Pure functions with expensive computation | Network 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.
