Deep dive into React useMemo — covering memoization fundamentals, dependency arrays, comparison with useCallback, React.memo, and practical examples for preventing expensive re-computations.
useMemo is one of React's most important performance hooks — but also one of the most misused. Developers reach for it when they shouldn't, and avoid it when they should. This guide cuts through the confusion with a clear mental model, concrete examples, and the updated React 19 perspective on memoization.
What Is useMemo?
useMemo memoizes the result of a computation. It re-runs the computation only when one of its dependencies changes:
import { useMemo } from 'react';
function ProductList({ products, filterText, sortBy }) {
const filteredAndSorted = useMemo(() => {
return products
.filter(p => p.name.toLowerCase().includes(filterText.toLowerCase()))
.sort((a, b) => {
if (sortBy === 'price') return a.price - b.price;
return a.name.localeCompare(b.name);
});
}, [products, filterText, sortBy]);
return (
<ul>
{filteredAndSorted.map(p => (
<li key={p.id}>{p.name} — ₹{p.price}</li>
))}
</ul>
);
}Without useMemo, the filter + sort operation runs on every render — even when neither products, filterText, nor sortBy changed. With it, React caches the result and only recomputes when a dependency actually changes.
The Signature
const memoizedValue = useMemo(
() => computeExpensiveValue(a, b), // factory function
[a, b] // dependency array
);The factory function runs during the first render
On subsequent renders, React compares the new dependencies to the previous ones using
Object.isIf all dependencies are identical, it returns the cached value without calling the factory
If any dependency changed, it calls the factory and caches the new result
When TO Use useMemo
1. Expensive computations
// Finding prime numbers — genuinely expensive
const primes = useMemo(() => {
return findPrimesUpTo(limit); // could take 50–200ms
}, [limit]);Rule of thumb: If the computation takes more than 1ms (measure it with console.time), consider memoization.
2. Stable object/array references for child component props
// Without useMemo, this object is recreated on every render
// causing Chart to re-render even when data hasn't changed
const chartOptions = useMemo(() => ({
responsive: true,
plugins: { legend: { position: 'top' } },
scales: { x: { type: 'linear' } },
}), []); // empty deps — object never changes
return <Chart data={data} options={chartOptions} />;3. Derived state from large datasets
const stats = useMemo(() => {
const total = transactions.reduce((sum, t) => sum + t.amount, 0);
const average = total / transactions.length;
const max = Math.max(...transactions.map(t => t.amount));
return { total, average, max };
}, [transactions]);When NOT to Use useMemo
This is where most developers go wrong. useMemo has overhead — it stores the cached value in memory and runs a dependency comparison on every render. For cheap computations, this overhead exceeds the savings.
// ❌ DON'T — this is faster without useMemo
const fullName = useMemo(() => `${firstName} ${lastName}`, [firstName, lastName]);
// ✅ DO — just compute it
const fullName = `${firstName} ${lastName}`;
// ❌ DON'T — primitive values don't benefit
const count = useMemo(() => items.length, [items]);
// ✅ DO
const count = items.length;useMemo vs useCallback
The confusion between these two is common:
// useMemo — memoizes a VALUE (the result of calling the function)
const sortedList = useMemo(() => [...items].sort(), [items]);
// useCallback — memoizes a FUNCTION (the function reference itself)
const handleSort = useCallback(() => {
setItems(prev => [...prev].sort());
}, []); // stable reference across rendersMental model: useMemo(() => fn(), deps) is equivalent to useCallback(fn, deps)(). Use useCallback when you need to pass a stable function reference to a child component or useEffect. Use useMemo when you need a cached computed value.
useMemo with React.memo
useMemo is most impactful when combined with React.memo for child components:
// React.memo prevents re-render if props haven't changed
const DataTable = React.memo(({ rows, columns }) => {
return (
<table>
{rows.map(row => (
<tr key={row.id}>
{columns.map(col => (
<td key={col.key}>{row[col.key]}</td>
))}
</tr>
))}
</table>
);
});
function Dashboard({ rawData, config }) {
// Without useMemo, rows and columns are new arrays every render
// React.memo won't help because props always look "different"
const rows = useMemo(() => transformRows(rawData), [rawData]);
const columns = useMemo(() => buildColumns(config), [config]);
return <DataTable rows={rows} columns={columns} />;
}The Dependency Array: Common Mistakes
Missing dependencies
// ❌ BUG — multiplier is used but not in deps
// This will use a stale multiplier value
const total = useMemo(() => {
return items.reduce((sum, item) => sum + item.price * multiplier, 0);
}, [items]); // missing: multiplier
// ✅ CORRECT
const total = useMemo(() => {
return items.reduce((sum, item) => sum + item.price * multiplier, 0);
}, [items, multiplier]);Object dependencies (always "changed")
// ❌ BUG — filters object is new reference every render
// useMemo never caches because {} !== {} in JS
function SearchResults({ query }) {
const filters = { query, page: 1 }; // new object each render
const results = useMemo(() => search(filters), [filters]); // useless!
// ✅ CORRECT — use primitive dependencies
const results = useMemo(() => search({ query, page: 1 }), [query]);
}React 19: Do We Still Need useMemo?
React 19 introduced the React Compiler (previously React Forget) which automatically adds memoization where beneficial. When the compiler is enabled, you'll need manual useMemo calls significantly less often.
However:
The React Compiler isn't universally available yet — many projects are on React 18
The compiler can't detect all memoization opportunities (external data, complex closures)
Understanding useMemo is still essential for debugging compiler output and edge cases
How to Profile Whether useMemo Actually Helps
Never add useMemo speculatively. Measure first:
// Quick measurement technique
const start = performance.now();
const result = expensiveComputation(input);
console.log('Computation took:', performance.now() - start, 'ms');
// If > 1ms and called frequently → useMemo is worth it
// If < 0.1ms → don't botherUse React DevTools Profiler to identify which components are re-rendering unnecessarily. That tells you where useMemo + React.memo combinations will have the most impact.
Quick Reference
Situation | Use useMemo? |
|---|---|
Filter/sort large array (>1000 items) | Yes |
String concatenation | No |
Stable object prop for React.memo child | Yes |
Primitive arithmetic (<100 operations) | No |
Cryptographic or statistical computations | Yes |
Derived state from large dataset | Yes |
Memoizing a function reference | Use useCallback instead |
Conclusion
React's useMemo is a targeted performance tool, not a blanket optimisation. Use it when computations are measurably expensive, when you need stable object references for memoized children, or when deriving complex state from large datasets. Skip it for simple expressions, primitives, and anywhere the React Compiler already handles it. Profile first, optimise second — always.









