An expensive computation called a hundred times with the same arguments produces the same result a hundred times. Redoing it on every call is wasted work.
Memoization applies one elementary idea: keep the answer the first time, hand it back afterward. The gain can take a computation from several seconds to a few microseconds.
Definition
Memoization means storing the result of a function in a cache keyed by its arguments. On the next call with the same values, the function is not run: the cache answers in its place.
function memoize(fn) {
const cache = new Map();
return function (argument) {
if (cache.has(argument)) return cache.get(argument);
const result = fn(argument);
cache.set(argument, result);
return result;
};
}
let calls = 0;
const square = memoize((n) => {
calls += 1;
return n * n;
});
console.log(square(12), square(12), calls); // 144 144 1The cache lives inside a Closure: only the returned function can reach it, and it survives from one call to the next. A Map accepts any kind of key, an object included.
Fibonacci, the demonstration that lands
A naive Recursion recomputes the same values thousands of times. The call counter shows it beyond argument.
let naive = 0;
function fib(n) {
naive += 1;
return n < 2 ? n : fib(n - 1) + fib(n - 2);
}
fib(25);
console.log(naive); // 242785
const cache = new Map();
let fast = 0;
function fibMemo(n) {
fast += 1;
if (cache.has(n)) return cache.get(n);
const value = n < 2 ? n : fibMemo(n - 1) + fibMemo(n - 2);
cache.set(n, value);
return value;
}
fibMemo(25);
console.log(fast); // 49242,785 calls against 49, for an identical result. The ratio is over four thousand, and it keeps growing with the requested value.
When not to memoize
- When the function is not pure. If the result depends on the clock or on outside state, the cache returns a stale answer. See Pure function.
- When arguments never repeat. The cache grows without ever being used.
- When the computation is already short. Looking the cache up sometimes costs more than redoing the work.
An unbounded cache is a slowly growing memory leak. In a long-lived application, cap the number of entries or use a WeakMap when the keys are objects.
Frequently asked questions
How do I handle several arguments?
The usual answer is to build a single key, often with JSON.stringify(arguments). It works, but it has a cost, and the key order of an object changes the resulting string. For two or three simple arguments, joining them with an unlikely separator stays faster and more predictable.
How is this different from a server cache?
Scope and lifetime. Memoization lives in the memory of the process or the tab, disappears on reload, and is never shared between users. A server cache survives requests and serves several clients, at the price of invalidation you have to manage explicitly.
Do interface libraries do the same thing?
The principle is identical, with one nuance: their cache usually keeps a single entry, the one from the last call, and clears as soon as the dependencies change. Our React course shows where that reflex genuinely helps, and where it adds code without speeding anything up.