Back to React
React
medium
senior

What are the common pitfalls of memo, useMemo, and useCallback in React?

Memoization adds bookkeeping cost for every render. It only pays off when the work is expensive AND the deps are actually stable AND a downstream consumer cares about identity. Most of the time it makes code noisier without measurable wins.

6 min read·~12 min to think through

React.memo, useMemo, useCallback aren't free. Each adds:

  • A dependency-array comparison every render.
  • An extra allocation for the cache slot.
  • Cognitive overhead for readers.

When memoization actually helps (all must be true):

  1. Expensive work — a real computation, not a string concat. Profile first.
  2. Stable deps — the inputs don't change every render. If they do, memo just adds bookkeeping for nothing.
  3. A consumer that cares about reference equality — e.g., React.memo child, useEffect dependency.

Common misuses:

  • Wrapping every callback in useCallback — but the receiving component isn't memoized, so reference stability changes nothing.
  • Memoizing primitivesuseMemo(() => count + 1, [count]) saves nothing.
  • Memoizing inside a parent that re-renders constantly — deps change every time; memo never hits.
  • Inline objects in depsuseMemo(..., [{ a }]) recreates the dep object every render; memo never hits.

When memoization can be net negative:

  • Tight loops where the comparison cost > the saved work.
  • High-churn render paths (e.g. animation frames) — allocations from cache slots add up.

React 19 + React Compiler auto-memoizes much of this; manual hooks become rarer. Until your codebase compiles, the heuristic is: profile, identify the wasted re-render, then surgically apply memoization to the smallest possible boundary.

Code

tsx
// ❌ The work is cheaper than the memo bookkeeping.
const fullName = useMemo(() => `${first} ${last}`, [first, last]);

// ✅ Just compute it.
const fullName = `${first} ${last}`;
useMemo that does nothing useful
tsx
function Parent() {
  const onClick = useCallback(() => save(), []); // stable…
  return <Child onClick={onClick} />;             // …but Child isn't memoized
}                                                 // so it re-renders anyway
useCallback wasted because child isn't memoized

Follow-up questions

  • What does the React Compiler do, and how does it affect manual memoization?
  • When should you use React.memo vs useMemo vs useCallback?
  • How do you measure whether a memoization actually helped?

Common mistakes

  • Wrapping everything 'just in case'.
  • Memoizing with deps that change every render — pointless.
  • Treating memoization as a guarantee — React may evict.

Performance considerations

  • Profile in a production build with React DevTools Profiler. The dev build distorts measurements.

Edge cases

  • useMemo's cache can be discarded by React under memory pressure — it's best-effort.
  • Inline arrays/objects in deps mean the memo never hits.

Real-world examples

  • React 19 announcement explicitly addresses memoization fatigue — the compiler is meant to make manual memo unnecessary in most cases.

Senior engineer discussion

Senior signal: discuss profiling-driven optimization, React Compiler's auto-memoization model, and how identity-based memoization differs from structural memoization (immer, immutable.js).

Related questions