Open this lesson in your favourite AI. It'll walk you through the why, explain the demo, and quiz you on the try-it list.
useMemo memoizes the result of a computation, recomputing it only when its dependencies change. The right use case is a value that is both expensive to compute and used in render — sorting a 10,000-item list, running a complex filter, or deriving a deeply nested structure from props. The common misuse is memoizing trivial computations where the overhead of the dependency comparison on every render exceeds the cost of just recomputing. The other trap: useMemo does not prevent re-renders of the component or its children — for that you need React.memo.
useMemo caches a computed value between renders. The cache is per-component-instance and clears when dependencies change, when the component unmounts, or when React decides to drop it (e.g. offscreen).
import { useState, useMemo } from 'react';
interface Item { id: number; name: string; score: number }
// CORRECT: expensive sort memoized — only re-runs when items or sortKey changes
export function SortedList({ items }: { items: Item[] }) {
const [sortKey, setSortKey] = useState<keyof Item>('score');
const sorted = useMemo(() => {
console.log('sorting...'); // watch how often this fires
return [...items].sort((a, b) =>
a[sortKey] > b[sortKey] ? 1 : -1
);
}, [items, sortKey]);
return (
<div>
<button onClick={() => setSortKey('name')}>Sort by name</button>
<button onClick={() => setSortKey('score')}>Sort by score</button>
<ul>{sorted.map(i => <li key={i.id}>{i.name}: {i.score}</li>)}</ul>
</div>
);
}
// WRONG: memoizing a trivial computation — overhead exceeds benefit
export function OverkillComponent({ n }: { n: number }) {
// Object.is comparison runs on every render regardless
// The "computation" (n * 2) is cheaper than useMemo's bookkeeping
const doubled = useMemo(() => n * 2, [n]); // pointless
return <div>{doubled}</div>;
}sorted directly). Add 50,000 items to the list. Increment the unrelated counter and measure the render time with React Profiler. Observe the sort running on every parent re-render. Re-add useMemo and confirm the performance improvement.useMemo(() => n * 2, [n]) in OverkillComponent to just n * 2. Use React Profiler to compare the render times. They should be identical or the unmemoized version may be faster — confirming that useMemo adds overhead for trivial work.items on every parent render (e.g. <SortedList items={[...data]} />). Watch 'sorting...' fire on every render despite useMemo — because the dependency check sees a new array reference each time. Fix it by stabilizing the reference with useMemo in the parent.Use these three in order. Each builds on the one before.
In one paragraph, explain what useMemo does and when you should reach for it. What's the most common mistake developers make with it?
Walk me through how React decides whether to return the cached value or recompute. What comparison does it use for dependencies, and what does that mean for object and array dependencies?
useMemo caches a single value per component instance. How does this interact with React's concurrent renderer, which may render a component multiple times before committing? Can useMemo's cached value be dropped between a render and a commit, and what does that mean for your code?