Back to React
React
easy
mid

What is the difference between useMemo and useCallback, and when do you use each?

`useMemo(fn, deps)` caches the *return value* of `fn`. `useCallback(fn, deps)` caches the *function itself*. `useCallback(fn, d)` is exactly `useMemo(() => fn, d)`. Use them for referential stability of values/functions passed to memoized children, and for genuinely expensive computations. In React 19+, the React Compiler handles most of these automatically.

5 min read·~10 min to think through

Same machinery, different return value.

ts
useMemo(() => compute(a, b), [a, b]);  // caches the result of compute()
useCallback((x) => fn(a, x), [a]);     // caches the function itself

useCallback(fn, d) is literally useMemo(() => fn, d). Two names so the intent is obvious at the call site.

Two reasons to use them.

1. Referential stability for memoized children.

React.memo skips re-rendering if props are shallow-equal. A new object/function each render breaks the bailout:

tsx
// Bad — Row.memo can never skip; new onClick every render
items.map(item => (
  <Row item={item} onClick={() => select(item.id)} />
))

// Good — stable handler
const onClick = useCallback((id) => select(id), [select]);
items.map(item => (
  <Row item={item} onClick={onClick} />
))

Same with values:

tsx
const config = useMemo(() => ({ theme, dense }), [theme, dense]);
<Header config={config} />

2. Genuinely expensive computations.

tsx
const sorted = useMemo(() => heavySort(items), [items]);

If items doesn't change, you skip the sort. Without useMemo, the sort runs every render — even when a sibling state triggered the render, not items.

Common misuse.

  • Wrapping cheap computations in useMemo. The hook itself has overhead (creating the deps array, comparing it). For a one-line transform, it's a wash or a loss.
  • Wrapping callbacks in useCallback when nothing memoized consumes them.
  • Passing primitives — useMemo(() => 42, []) is silly; primitives are already referentially equal.
  • Trusting memoization to fix a state-in-the-wrong-place problem. Lift state down instead.

The dep-array footgun.

ESLint's react-hooks/exhaustive-deps rule will tell you. Listen to it. Disabling it because "I know what I'm doing" is the source of 80% of stale-closure bugs in production:

tsx
// Bug — onClick captures the initial value of count forever
const onClick = useCallback(() => {
  console.log(count); // stale
}, []); // missing dep: count

The React Compiler changes the rules (React 19+).

In compiler-on codebases, you mostly don't write these hooks anymore. The compiler analyses your component, infers stability needs, and auto-memoizes. Manual hooks are reserved for:

  • Genuinely heavy computations the compiler can't see is heavy.
  • Cases outside the compiler's analysis scope (interop with non-compiled code).

The "memoize every callback to be safe" reflex is now actively unhelpful — it adds noise and can interact badly with the compiler.

Senior cheat sheet.

GoalPre-compilerWith compiler
Stable callback for memoized childuseCallbacknothing
Expensive computeuseMemouseMemo (still useful)
Stable object/array propuseMemonothing
Reset subtree on key change<C key={...} /><C key={...} />

Quick disambiguation in interviews. "useMemo memoizes values, useCallback memoizes functions, useCallback is shorthand for useMemo wrapping a function literal." If they ask for use cases, name the memoized child / referential equality pattern first — that's the right answer for ~80% of real usage.

Follow-up questions

  • When is `useMemo` cheaper than just recomputing?
  • Why doesn't `useCallback` alone speed anything up?
  • What does the React Compiler change about manual memoization?
  • How would you debug a stale-closure bug from missing deps?

Common mistakes

  • Wrapping every function in useCallback even when no memoized child consumes it.
  • Disabling the `exhaustive-deps` lint rule to suppress warnings.
  • Memoizing primitives.
  • Believing useMemo *guarantees* the cached value will persist — React may discard caches under memory pressure.

Performance considerations

  • Hook overhead is small but non-zero — not free, especially in lists.
  • Profile before adding either; most components don't need them.

Edge cases

  • React may drop memoized values under memory pressure — don't rely on identity for correctness.
  • Strict Mode runs hooks twice; useMemo callbacks must be pure (no side effects).

Real-world examples

  • Lists with thousands of memoized rows — useCallback for shared handlers.
  • Charts deriving from raw data — useMemo for the transform.

Related questions