Topics in this subject
React 4 min read Updated 5 Aug 2026

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:

  1. Memoized children (React.memo) do a shallow prop compare—a stable reference lets them skip re-rendering.
  2. Dependency arrays of useEffect/useMemo/useCallback compare by Object.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-deps lint 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.