useMemo vs useCallback vs React.memo: When to Use Each
Three React APIs get reached for the moment someone says “let's optimize this”: useMemo, useCallback, and React.memo. They sound interchangeable. They're not — and reaching for the wrong one, or all three everywhere, is how you end up with code that's harder to read and no faster. Here's the mental model I use to pick the right one in a second.

The short version
- useMemo memoizes a value — the result of an expensive calculation, so it isn't recomputed on every render.
- useCallback memoizes a function — it keeps the same function identity between renders so children don't see a “new” prop.
- React.memo memoizes a component — it skips a child's re-render when its props are shallow-equal to last time.
Value, function, component. Once you tie each API to what it wraps, the choice stops being a guess.
useMemo — cache a computed value
useMemo runs your function and remembers its return value, recomputing only when a dependency changes. Use it when a calculation is genuinely expensive, or when the value's identity matters downstream.
const sortedRows = useMemo(() => {
// re-sorts only when `rows` changes, not on every render
return [...rows].sort((a, b) => b.score - a.score);
}, [rows]);Two legitimate reasons to reach for it: the computation is heavy (sorting or filtering a large list, parsing, derived aggregates), or the result is an object/array you pass to a memoized child or a hook dependency array — where a fresh reference every render would defeat the memoization.
useCallback — keep a function's identity stable
Every render creates brand-new function objects. Usually that's harmless. It stops being harmless the moment that function is a prop to a React.memo child or a dependency of another hook — a new identity each render silently breaks the optimization.
const handleSelect = useCallback((id: string) => {
setSelected(id);
}, []); // same function identity across rendersuseCallback(fn, deps) is really just useMemo(() => fn, deps) with nicer ergonomics for functions. If nothing consumes the function's identity, wrapping it buys you nothing.
React.memo — skip a child's re-render
By default a component re-renders whenever its parent does, even if its props are identical. React.memo wraps a component and does a shallow compare of props: same props, no re-render.
const Row = React.memo(function Row({ label, onSelect }) {
return <li onClick={onSelect}>{label}</li>;
});The catch: a shallow compare means object, array, and function props must keep a stable identity — which is exactly why React.memo so often needs useMemo and useCallback beside it. They're a team, not competitors.
Cheat sheet
| You want to… | Reach for | It wraps |
|---|---|---|
| Avoid recomputing an expensive value | useMemo | A value |
| Keep a callback's identity stable for a child/hook | useCallback | A function |
| Stop a child re-rendering on unchanged props | React.memo | A component |
When memoization makes things worse
Memoization isn't free. Every useMemo and useCallback stores a value plus its dependency array and runs a comparison on each render. For a cheap calculation, that bookkeeping can cost more than just recomputing. Wrapping everything “to be safe” adds noise, hides real bottlenecks, and can even slow renders down.
The rule I follow: don't memoize on spec — memoize when the profiler tells you to. If a value is a number from a quick expression, or a function nothing memoized consumes, leave it plain.
The mistake that quietly breaks everything

The bug that's bitten me more than once isn't missing memoization — it's memoization that quietly does nothing because a dependency picks up a fresh identity on every render:
// ❌ options is a new object every render, so this never hits
const options = { sort: true };
const config = useMemo(() => build(options), [options]);
// ✅ stabilize the dependency first
const options = useMemo(() => ({ sort: true }), []);
const config = useMemo(() => build(options), [options]);The whole game is referential identity. A child wrapped in React.memo still re-renders if you hand it a fresh object or an inline arrow function every render. That interplay — stable props feeding memoized children — is the same thing I dug into in how I cut unnecessary React re-renders by 40%.
The takeaway
Match the tool to what it wraps — value, function, component — and only reach for it when something actually told you to, which usually means the profiler did. The combo that's earned its keep in my own code: React.memo on a heavy child, fed props you kept stable with useMemo and useCallback. Scatter all three at random and you haven't optimized anything — you've just added ceremony.
FAQ
Is useCallback just useMemo for functions?
Essentially yes. useCallback(fn, deps) is equivalent to useMemo(() => fn, deps). useCallback returns the function itself; useMemo returns whatever the function returns.
Should I wrap every component in React.memo?
No. React.memo only helps when a component re-renders often with the same props and its render isn't trivial. Applied everywhere, the prop-comparison overhead can outweigh the savings.
Does the React Compiler make these obsolete?
It reduces the need for manual memoization by auto-memoizing where it's safe, but understanding value-vs-function-vs-component still matters for reading code, debugging, and every codebase not yet on it.