React useMemo Benchmarks: When Memoization Actually Wins
useMemo is the most cargo-culted hook in React — wrapped around everything, understood by few. Used precisely, it delivered a 1.5–2x dashboard response improvement on a data-heavy admin panel. Used reflexively, it makes apps slower while looking like optimization. The difference is knowing exactly what it costs and what it buys.
What it actually does
useMemo caches a computed value between renders, recomputing only when its dependencies change. That is the entire mechanic. It is not free: it adds a dependency comparison and a memory slot on every render. For a cheap computation, that overhead exceeds the work you are caching — you have paid to avoid a cost smaller than the payment.
Where it earns its keep
Two cases justify it. First, a genuinely expensive computation — sorting, filtering, or aggregating a large dataset on every render. Second, preserving referential identity so a memoized child or an effect dependency does not re-run when the value is logically unchanged. Outside those, you are adding indirection for no measurable gain.
// memo earns its keep on the expensive aggregation
const rollup = useMemo(
() => aggregate(rows), // O(n) over thousands of rows
[rows]
);
// NOT this — the work is cheaper than the comparison
// const label = useMemo(() => `${first} ${last}`, [first, last]);Measure, then memoize
The only honest way to apply it is with the Profiler open. Find the components that actually re-render expensively, memoize those, and verify the flame graph got shorter. The 2x win on that dashboard came from memoizing a handful of heavy selectors — not from blanketing the tree. Optimization you did not measure is just superstition with a hook.
useMemo is a trade: memory and a comparison in exchange for skipped work. If the skipped work is cheaper than the comparison, you made the app slower and called it fast.For the broader state-management picture, see State Management in the AI Era.