Loading...
Loading...
By continuing to use the platform, you accept the terms of the Privacy Policy and the use of cookies.
👋 Hi! I'm Dastan, author of Hack Frontend. Always open to professional networking => connect with me on LinkedIn.
Memoization is an optimization technique that stores (caches) the results of function execution for specific arguments. On subsequent calls with the same arguments, the function returns the cached result instead of recomputing.
Memoization is "remembering" the results of expensive computations so you don't have to redo them.
function memoize(fn) {
const cache = {}; // Object to store results
return function(...args) {
const key = JSON.stringify(args); // Key from arguments
if (key in cache) {
console.log('From cache');
return cache[key];
}
console.log('Computing');
const result = fn(...args);
cache[key] = result;
return result;
};
}
// Usage
function expensiveSum(a, b) {
// Simulate long operation
for (let i = 0; i < 1000000000; i++) {}
return a + b;
}
const memoizedSum = memoize(expensiveSum);
console.time('First call');
memoizedSum(5, 10); // Computing
console.timeEnd('First call'); // ~1000ms
console.time('Second call');
memoizedSum(5, 10); // From cache
console.timeEnd('Second call'); // ~0ms
function fibonacci(n) {
if (n <= 1) return n;
return fibonacci(n - 1) + fibonacci(n - 2);
}
console.time('fib');
fibonacci(40); // ~1.5 seconds
console.timeEnd('fib');
Problem: Function computes the same values many times.
fib(3) computed 2 times, fib(2) — 3 times!
const fibonacci = memoize((n) => {
if (n <= 1) return n;
return fibonacci(n - 1) + fibonacci(n - 2);
});
console.time('fib');
fibonacci(40); // ~0.5 milliseconds
console.timeEnd('fib');
Result:
Speed increases by thousands of times for large N values.
function memoize(fn, maxSize = 100) {
const cache = new Map();
return function(...args) {
const key = JSON.stringify(args);
if (cache.has(key)) {
return cache.get(key);
}
const result = fn(...args);
// Limit cache size
if (cache.size >= maxSize) {
const firstKey = cache.keys().next().value;
cache.delete(firstKey); // Remove oldest
}
cache.set(key, result);
return result;
};
}
function memoizeWithTTL(fn, ttl = 5000) {
const cache = new Map();
return function(...args) {
const key = JSON.stringify(args);
const cached = cache.get(key);
if (cached && Date.now() - cached.timestamp < ttl) {
return cached.value;
}
const result = fn(...args);
cache.set(key, {
value: result,
timestamp: Date.now()
});
return result;
};
}
// Usage
const fetchUser = memoizeWithTTL(async (id) => {
const response = await fetch(`/api/users/${id}`);
return response.json();
}, 10000); // Cache for 10 seconds
function memoizeLRU(fn, maxSize = 100) {
const cache = new Map();
return function(...args) {
const key = JSON.stringify(args);
if (cache.has(key)) {
const value = cache.get(key);
// Move to end (mark as recently used)
cache.delete(key);
cache.set(key, value);
return value;
}
const result = fn(...args);
cache.set(key, result);
// Remove oldest (first) element
if (cache.size > maxSize) {
const firstKey = cache.keys().next().value;
cache.delete(firstKey);
}
return result;
};
}
Memoizes entire component — skips re-render if props haven't changed.
import { memo } from 'react';
const ExpensiveComponent = memo(({ data }) => {
console.log('Rendering ExpensiveComponent');
// Heavy computations
const processed = processData(data);
return <div>{processed}</div>;
});
// Component re-renders only if data changed
const UserCard = memo(
({ user }) => {
return <div>{user.name}</div>;
},
(prevProps, nextProps) => {
// Return true if should NOT update
return prevProps.user.id === nextProps.user.id;
}
);
Memoizes computation result inside component.
import { useMemo } from 'react';
function ProductList({ products, filterText }) {
const filteredProducts = useMemo(() => {
console.log('Filtering products');
return products.filter(p =>
p.name.toLowerCase().includes(filterText.toLowerCase())
);
}, [products, filterText]);
return (
<ul>
{filteredProducts.map(p => (
<li key={p.id}>{p.name}</li>
))}
</ul>
);
}
Important:
useMemo recalculates value only when dependencies in array [products, filterText] change.
Memoizes the function itself (to avoid creating new function on each render).
import { useCallback, memo } from 'react';
const Button = memo(({ onClick, children }) => {
console.log(`Rendering button "${children}"`);
return <button onClick={onClick}>{children}</button>;
});
function Parent() {
const [count, setCount] = useState(0);
const [other, setOther] = useState(0);
// Creates new function on each render
const handleClick = () => setCount(count + 1);
// Function created once
const handleClickMemo = useCallback(() => {
setCount(prev => prev + 1);
}, []);
return (
<>
<Button onClick={handleClickMemo}>Count: {count}</Button>
<button onClick={() => setOther(other + 1)}>Other: {other}</button>
</>
);
}
// Not needed
const doubled = useMemo(() => count * 2, [count]);
// Better just
const doubled = count * 2;
// Bad - API call will be cached forever
const fetchData = memoize(async (id) => {
return await fetch(`/api/users/${id}`);
});
// If function called rarely with different arguments
const memoizedSort = memoize(arr => [...arr].sort());
// Cache will grow infinitely
import { memoize } from 'lodash';
const expensiveFn = memoize((a, b) => {
return a + b;
});
// Can set custom resolver for key
const memoized = memoize(
(obj) => obj.value,
(obj) => obj.id // Cache key
);
import memoize from 'fast-memoize';
const fn = memoize((a, b) => a + b);
import { createSelector } from 'reselect';
const getUsers = state => state.users;
const getFilter = state => state.filter;
const getFilteredUsers = createSelector(
[getUsers, getFilter],
(users, filter) => users.filter(u => u.name.includes(filter))
);
// Result is cached
// Objects with same data but different references
const obj1 = { id: 1 };
const obj2 = { id: 1 };
JSON.stringify(obj1) === JSON.stringify(obj2); // true, but slow
// Better to use primitives as keys
function memoize(fn) {
const cache = new Map();
return function(id) { // Only primitive argument
if (cache.has(id)) return cache.get(id);
const result = fn(id);
cache.set(id, result);
return result;
};
}
// Cache grows infinitely
const memoized = memoize(expensiveFn);
// Limit size or add TTL
const memoized = memoizeLRU(expensiveFn, 100);
// useMemo won't work with object mutation
const obj = { count: 0 };
obj.count++; // Mutation - same reference
// Create new object
setObj({ ...obj, count: obj.count + 1 });
Memoization:
In Interviews:
Important to be able to:
memoize functionuseMemo, useCallback, and React.memo