- Home
- /
- Tutorials
- /
- JavaScript Tutorial
- /
- JavaScript Memoization
JavaScript Performance
JavaScript Memoization
Memoization caches a function's results so the same input never does the same work twice.
It trades memory for speed.
It only helps for a pure function, where the same input always gives the same output.
Example
Example
javascript
function memoize(fn) {
const cache = new Map();
return function (n) {
if (cache.has(n)) {
return cache.get(n);
}
const result = fn(n);
cache.set(n, result);
return result;
};
}
let calls = 0;
const double = memoize(function (n) {
calls++;
return n * 2;
});
double(5);
double(5);
double(5);
console.log(calls);The output is 1: the real function ran only for the first call.
Caching by Argument
The first time an input is seen, the function runs and the result is stored.
Every later call with that same input returns the stored result instantly.
A Map is a natural cache, keyed by the argument.
Syntax
Syntax
javascript
const memoized = memoize(expensiveFunction);Only worth it for a function that is genuinely expensive to call.
Seeing the Cache Work
Repeated calls with the same input never reach the real function again.
Example
Example
javascript
function memoize(fn) {
const cache = new Map();
return function (n) {
if (cache.has(n)) return cache.get(n);
const result = fn(n);
cache.set(n, result);
return result;
};
}
let calls = 0;
const square = memoize(function (n) {
calls++;
return n * n;
});
console.log(square(4));
console.log(square(4));
console.log(calls);The output is 16, 16, 1.
Different Inputs Are Cached Separately
Each distinct argument gets its own cache entry.
Example
Example
javascript
function memoize(fn) {
const cache = new Map();
return function (n) {
if (cache.has(n)) return cache.get(n);
const result = fn(n);
cache.set(n, result);
return result;
};
}
let calls = 0;
const square = memoize(function (n) {
calls++;
return n * n;
});
square(2);
square(3);
square(2);
console.log(calls);The output is 2: two distinct inputs, two real calls.
Speeding Up Recursion
Naive recursive Fibonacci repeats the same calculation constantly without a cache.
Example
Example
javascript
function memoize(fn) {
const cache = new Map();
const memoized = function (n) {
if (cache.has(n)) return cache.get(n);
const result = fn(n);
cache.set(n, result);
return result;
};
return memoized;
}
let fib;
fib = memoize(function (n) {
return n <= 1 ? n : fib(n - 1) + fib(n - 2);
});
console.log(fib(20));Without memoizing, this would repeat the same smaller calls thousands of times.
Keys Beyond a Single Number
For several arguments, build a single string key from all of them.
Example
Example
javascript
function memoize(fn) {
const cache = new Map();
return function (a, b) {
const key = a + "," + b;
if (cache.has(key)) return cache.get(key);
const result = fn(a, b);
cache.set(key, result);
return result;
};
}
let calls = 0;
const add = memoize(function (a, b) {
calls++;
return a + b;
});
add(2, 3);
add(2, 3);
console.log(calls);The output is 1, even though two arguments are involved.
When Not to Memoize
A function with side effects, or one that depends on more than its arguments, cannot be cached safely.
Caching a rarely-repeated call also just wastes memory for no benefit.
Example
Example
javascript
function notPure() {
return Math.random();
}
console.log(typeof notPure());Memoizing this would return the same random value forever, which is wrong.
Complete Example
Complete Example
html
<!DOCTYPE html>
<html>
<head>
<title>JavaScript Memoization</title>
</head>
<body>
<h1>Memoization</h1>
<p id="out"></p>
<script>
function memoize(fn) {
const cache = new Map();
return function (n) {
if (cache.has(n)) {
return cache.get(n);
}
const result = fn(n);
cache.set(n, result);
return result;
};
}
let realCalls = 0;
const slowSquare = memoize(function (n) {
realCalls++;
return n * n;
});
slowSquare(5);
slowSquare(5);
slowSquare(5);
document.getElementById("out").textContent =
"Result: " + slowSquare(5) + ", real calls: " + realCalls;
</script>
</body>
</html>Try It Yourself
Run the above example in the Try It Editor.
Try a new input:
Call slowSquare(6) as well and watch realCalls go up by one.
Important Points
- Memoization caches a function's results by its input.
- A
Mapmakes a natural cache. - Only a pure function can be memoized safely.
- Multiple arguments need a combined key.
- It is not worth it for a cheap or rarely-repeated call.
Conclusion
Memoization is a small pattern with a large effect on repeated expensive work.
Recursive functions like Fibonacci show the effect most dramatically.
Remember that it only ever helps for pure functions.
