useMemo & useCallback
useMemo caches a value, useCallback caches a function reference—both for referential stability, not raw speed.
Both hooks cache something across renders keyed by a dependency array. useMemo caches a computed value; useCallback caches a function reference. The primary goal is usually referential stability, so that dependency arrays and React.memo children don't see a "new" value every render.
The two hooks#
// useMemo — recomputes only when [a, b] change
const sorted = useMemo(() => bigList.slice().sort(cmp), [bigList]);
// useCallback — same function reference until [query] changes
const onSearch = useCallback((e) => fetchResults(query, e.target.value), [query]);
// Identity: useCallback(fn, deps) === useMemo(() => fn, deps)
🎯 useCallback(fn, deps) is exactly useMemo(() => fn, deps). useCallback exists purely as sugar for memoizing a function without the extra arrow.
Why referential stability matters#
flowchart TD
P["Parent re-renders"] --> Q{"Is child prop a new reference?"}
Q -->|"inline {} or () => {}"| NEW["New reference every render"]
Q -->|"useMemo / useCallback"| STABLE["Stable reference"]
NEW --> R["React.memo child re-renders anyway ⚠️"]
STABLE --> S["React.memo child skips render 🟢"]
STABLE --> T["useEffect deps stay equal → effect doesn't re-run"]
Two concrete payoffs:
- Memoized children (
React.memo) do a shallow prop compare—a stable reference lets them skip re-rendering. - Dependency arrays of
useEffect/useMemo/useCallbackcompare byObject.is—a stable reference prevents effects from firing every render.
const Child = React.memo(function Child({ onClick }) { /* … */ });
function Parent({ query }) {
// ⚠️ Without useCallback, onClick is new each render → Child re-renders despite memo
const onClick = useCallback(() => console.log(query), [query]);
return <Child onClick={onClick} />;
}
useMemo vs useCallback#
useMemo |
useCallback |
|
|---|---|---|
| Caches | Return value of the function | The function itself |
| Signature | useMemo(() => value, deps) |
useCallback(fn, deps) |
| Runs the function? | Yes, during render (returns result) | No, returns fn for later |
| Typical use | Expensive computation, stable object/array prop | Stable callback for memoized child or effect dep |
| Equivalence | — | useMemo(() => fn, deps) |
Dependency arrays ⚠️#
- List every reactive value referenced inside. Omitting deps → stale closures. The
react-hooks/exhaustive-depslint rule enforces this. []= compute once and never recompute (deps never change).- Deps are compared with
Object.is; objects/arrays/functions compare by reference, so an unstable dep defeats the memo.
// ⚠️ stale: uses `count` but doesn't list it
const fn = useCallback(() => console.log(count), []);
// 🟢
const fn = useCallback(() => console.log(count), [count]);
Don't wrap everything ⚠️#
These hooks are not free: they cost memory, a deps-array comparison every render, and code complexity. For a cheap computation or a callback passed to a non-memoized DOM element, the memo often costs more than it saves.
🟢 Profile first. Reach for them when (a) the computation is genuinely expensive, or (b) you need referential stability for a memoized child or an effect dependency. Otherwise skip them.
React Compiler note 🎯#
The React Compiler (React 19-era) automatically memoizes values and callbacks at build time by analyzing your components. With it enabled, most manual useMemo/useCallback becomes unnecessary—you write plain code and the compiler inserts memoization. Still understand the hooks for interviews, legacy code, and cases the compiler bails out of.
Interview Q&A#
Q1. What is the difference between useMemo and useCallback?
useMemo runs a function and caches its return value; useCallback caches the function reference itself. useCallback(fn, deps) equals useMemo(() => fn, deps).
Q2. What is the main reason to use these hooks—raw performance?
Usually referential stability, not raw speed: keeping a stable reference so React.memo children can skip re-renders and effect dependency arrays don't fire every render. Expensive-computation caching is the secondary case.
Q3. Why might useCallback fail to stop a child from re-rendering?
Because the child isn't wrapped in React.memo (so it re-renders regardless of prop identity), or because one of the callback's dependencies is itself unstable, changing the reference every render.
Q4. What's the downside of wrapping everything in useMemo/useCallback? Each adds memory overhead, a dependency comparison every render, and code noise. For cheap work or non-memoized consumers the overhead exceeds the benefit—profile before optimizing.
Q5. How does the React Compiler change this advice?
It auto-memoizes values and functions at build time, making most manual useMemo/useCallback unnecessary. You still need to understand them for legacy code and compiler bail-out cases.