Memoisation is an optimisation technique that caches function results to avoid recomputing expensive operations. When the same inputs occur again, return the cached result instead of recalculating. It is particularly powerful for recursive functions like fibonacci, expensive computations, and React's useMemo hook. Master memoisation and your code will run dramatically faster.
The problem: expensive repeated calculations
Without memoisation, calling a function with the same inputs multiple times forces you to recompute the result every time — wasting CPU cycles.
| // ❌ Without memoisation: recomputes every time function fibonacci(n) { if (n <= 1) return n; return fibonacci(n - 1) + fibonacci(n - 2); } fibonacci(40); → Takes ~2 seconds fibonacci(40); → Takes ~2 seconds again (computed from scratch!) // ✅ With memoisation: cached result is instant const memoFib = memoise(fibonacci); memoFib(40); → Takes ~2 seconds (first call) memoFib(40); → Instant! ← returned from cache |
Basic memoisation with a cache object
The simplest memoisation stores results in an object (Map) using inputs as keys. If the key exists, return the cached value. Otherwise, compute and store it.
| // Simple memoisation for single-argument functions function slowFib(n) { if (n <= 1) return n; return slowFib(n - 1) + slowFib(n - 2); } const cache = {}; function memoFib(n) { if (n in cache) { console.log(`Returning cached result for ${n}`); return cache[n]; } console.log(`Computing fibonacci(${n})`); const result = slowFib(n); cache[n] = result; ← store in cache return result; } memoFib(5); → Computing fibonacci(5) ... 5 memoFib(5); → Returning cached result for 5 ... 5 memoFib(6); → Computing fibonacci(6) ... 8 |
Generic memoisation helper
A generic memoisation function works with any function, including those with multiple arguments. It serializes arguments into a cache key.
| // Generic memoise function for any function function memoise(fn) { const cache = new Map(); return function(...args) { const key = JSON.stringify(args); ← serialize args to key if (cache.has(key)) { return cache.get(key); } const result = fn(...args); cache.set(key, result); return result; }; } // Works with any function const add = (a, b) => a + b; const memoAdd = memoise(add); memoAdd(2, 3); → 5 ← computed memoAdd(2, 3); → 5 ← from cache memoAdd(3, 4); → 7 ← new cache key |
Real-world memoisation examples
Expensive API calls
| const memoise = (fn) => { const cache = new Map(); return (...args) => { const key = JSON.stringify(args); if (cache.has(key)) return cache.get(key); const result = fn(...args); cache.set(key, result); return result; }; }; const fetchUser = async (id) => { const response = await fetch(`/api/users/${id}`); return response.json(); }; const cachedFetchUser = memoise(fetchUser); await cachedFetchUser(1); → ~200ms API call await cachedFetchUser(1); → Instant! ← from cache |
Expensive calculations (fibonacci)
| // Recursive fibonacci (slow without memoisation) function fib(n) { if (n <= 1) return n; return fib(n - 1) + fib(n - 2); ← exponential time! } const memoFib = memoise(fib); // Now it is FAST thanks to memoisation memoFib(50); → Instant! ← previously took minutes // Why? Each fib(n) is computed once, then cached // fib(50) needs fib(49) + fib(48) // fib(49) needs fib(48) + fib(47) — but fib(48) is cached! |
Memoisation with cache size limits (LRU)
Unbounded caches can consume infinite memory. An LRU (Least Recently Used) cache keeps only the most recent N results, evicting old ones.
| // Memoise with size limit function memoiseWithLimit(fn, limit = 10) { const cache = new Map(); return function(...args) { const key = JSON.stringify(args); if (cache.has(key)) { cache.delete(key); ← move to end (most recent) cache.set(key, cache.get(key)); return cache.get(key); } const result = fn(...args); cache.set(key, result); // Evict oldest if over limit if (cache.size > limit) { const firstKey = cache.keys().next().value; cache.delete(firstKey); } return result; }; } const memoFib = memoiseWithLimit(fib, 100); // Cache will never grow beyond 100 entries |
Key takeaways
Memoisation caches function results to avoid recomputing expensive operations. Given the same inputs, return the cached result instead of recalculating. A generic memoisation function can wrap any function and transparently cache its results. Serialize arguments to JSON to create cache keys. Unbounded caches can consume memory, so implement LRU (Least Recently Used) eviction for production use. Memoisation is particularly powerful for recursive functions, expensive computations, and frontend frameworks like React.