How do you implement function memoization?
1. Basic implementation (for one function with primitive arguments)
function memoize(fn) {
const cache = {};
return function (...args) {
const key = JSON.stringify(args); // build a key from the arguments
if (key in cache) {
console.log('Taking from cache:', key);
return cache[key];
}
console.log('Computing:', key);
const result = fn(...args);
cache[key] = result;
return result;
};
}Usage example:
function slowAdd(a, b) {
// simulate heavy computation
for (let i = 0; i < 1e8; i++);
return a + b;
}
const memoAdd = memoize(slowAdd);
console.log(memoAdd(2, 3)); // Computing
console.log(memoAdd(2, 3)); // Taking from cache
console.log(memoAdd(4, 5)); // ComputingNow repeated calls with the same arguments are not recomputed, they are simply pulled from memory.
2. A universal implementation (with Map instead of an object)
It is better to use
Map, because it is faster and more reliable for keys of any structure.
function memoize(fn) {
const cache = new Map();
return function (...args) {
const key = JSON.stringify(args);
if (cache.has(key)) {
return cache.get(key);
}
const result = fn(...args);
cache.set(key, result);
return result;
};
}Benefit: you can safely cache values even for complex arguments (arrays, objects).
3. An advanced version with a cache size limit (LRU cache)
So the cache does not grow forever and does not "eat" memory.
function memoize(fn, limit = 5) {
const cache = new Map();
return function (...args) {
const key = JSON.stringify(args);
if (cache.has(key)) {
// update the usage order
const value = cache.get(key);
cache.delete(key);
cache.set(key, value);
return value;
}
const result = fn(...args);
cache.set(key, result);
// if the cache is too big, remove the oldest entry
if (cache.size > limit) {
const oldestKey = cache.keys().next().value;
cache.delete(oldestKey);
}
return result;
};
}Now the cache is a "sliding" one: it stores the last N calls, which is useful for real applications.
4. A real-world example, memoizing a recursive function (fibonacci)
function memoize(fn) {
const cache = {};
return function (n) {
if (n in cache) return cache[n];
const result = fn(n);
cache[n] = result;
return result;
};
}
const fib = memoize(function f(n) {
if (n <= 1) return n;
return f(n - 1) + f(n - 2);
});
console.log(fib(40)); // Fast, despite the recursionWithout memoization this would take tens of millions of calls, but with the cache, just 40.
5. An implementation supporting asynchronous functions
When you need to cache the results of
fetch,axios,db.query, and so on.
function memoizeAsync(fn) {
const cache = new Map();
return async function (...args) {
const key = JSON.stringify(args);
if (cache.has(key)) return cache.get(key);
const promise = fn(...args).then(result => {
cache.set(key, result);
return result;
});
cache.set(key, promise);
return promise;
};
}Example:
const fetchUser = memoizeAsync(async (id) => {
const res = await fetch(`https://jsonplaceholder.typicode.com/users/${id}`);
return res.json();
});
await fetchUser(1); // first time, an HTTP request
await fetchUser(1); // second time, instant, from the cache6. When and where to use memoization
| Scenario | Fits? | Why |
|---|---|---|
| Expensive computations | Yes | Speeds up repeat calls |
| Repeating arguments | Yes | The cache pays off |
| Different arguments every time | No | The cache is useless |
| HTTP requests, a database | Caution | OK if the data rarely changes |
| Large data | Caution | Watch memory usage |
Summary
Memoization is caching a function's results to speed up repeat calls. In JS it is implemented through a closure, a store (
Map/Object), and serializing the arguments (JSON.stringify).
Short Answer
Interview readyA concise answer to help you respond confidently on this topic during an interview.