Home/Learn/React/memo, useMemo and useCallback

memo, useMemo and useCallback

Advanced
Patterns

Memoisation trades memory and complexity for skipped work. Applied without measuring, it usually makes code slower to read and no faster to run.

Overview

These three tools exist to skip work that would otherwise repeat, and they are the most cargo-culted APIs in React. useCallback wrapped around every handler and useMemo around every value adds real cost — the comparison itself, the retained references, and the noise in the code — while fixing a problem that in most components does not exist. The correct order is: make it correct, measure, find the actual cost, then apply memoisation at that specific point. React Compiler is now doing much of this automatically, which makes the manual habit even less defensible.

What Each One Does

Three different things people conflate.

A component, a value, a function identity
// memo — skip re-rendering a component when its props are shallow-equal
const ProblemRow = memo(function ProblemRow({ problem, onOpen }) { ... })

// useMemo — cache a computed VALUE between renders
const stats = useMemo(() => computeStats(attempts), [attempts])

// useCallback — cache a FUNCTION identity between renders
const onOpen = useCallback((slug) => navigate(`/p/${slug}`), [navigate])

// The three work together or not at all. memo compares props with
// Object.is, so a new function or object prop defeats it:
const Row = memo(RowImpl)
<Row problem={p} onOpen={() => open(p.id)} />     // new arrow each render
                                                   // -> memo never skips

// Both halves are required: memo on the child AND stable props.
// One without the other is pure cost.

When It Is Justified

The specific situations, and how to confirm you are in one.

Four cases, and measure first
// 1. A genuinely expensive computation
const filtered = useMemo(
  () => rows.filter(matches).sort(compare),      // 10,000+ rows
  [rows, query],
)
// Filtering 50 rows is microseconds. Measure before assuming.

// 2. A value used as a dependency, where a new identity would
//    re-run an effect every render
const options = useMemo(() => ({ topic, limit }), [topic, limit])
useEffect(() => subscribe(options), [options])

// 3. Props to a memoised child that re-renders expensively —
//    a chart, a large list, a code editor

// 4. Passing a value through context (memoise the provider value)

// How to confirm: React DevTools Profiler. Record the interaction,
// look for components with a long actual render time, and check
// "why did this render". If nothing is slow, do nothing.

Cheaper Alternatives

Restructuring usually beats memoising, and the compiler may make the question moot.

Move state down, pass children, or let the compiler do it
// Move state down — the expensive sibling stops re-rendering
function Page() {
  return <><SearchBox /><HugeList /></>       // SearchBox owns the query
}
// Previously, query state in Page re-rendered HugeList on every keystroke.

// Pass children through — the child element is created by the
// parent's parent, so it is not recreated by this render
function Layout({ children }) {
  const [open, setOpen] = useState(false)
  return <div>{children}</div>          // children do not re-render
}

// Compute during render instead of caching:
// derived values are usually cheap, and always correct.

// React Compiler (React 19) memoises automatically at build time,
// which removes most manual useMemo and useCallback. Where it is
// enabled, hand-written memoisation is mostly noise.

// And the cost of getting it wrong: a stale closure inside a
// useCallback with missing dependencies is a real bug, where no
// memoisation at all would merely have been slower.

Key Points to Remember

  • 1memo skips a re-render, useMemo caches a value, useCallback caches a function identity
  • 2memo is defeated by a new object or arrow function prop — both halves are needed or neither helps
  • 3Justified cases: genuinely expensive computation, dependency identity, memoised expensive children, and context values
  • 4Confirm with the React DevTools Profiler rather than memoising on suspicion
  • 5Moving state down or passing children usually beats memoising, and React Compiler removes most manual cases

Interview Questions

Sign in to ask Aria
1

What is the difference between useMemo and useCallback?

Easy
2

Why does wrapping a child in memo often fail to prevent re-renders?

Hard
3

What is the cost of memoising everything by default?

Medium

Ask Aria about memo, useMemo and useCallback

Your personal AI tutor — ask anything about this concept

Revision Status

Personal Notes

Sign in to save personal notes for this topic.

Discussion

Sign in to join the discussion.

Loading discussion…