When to Memoize in React: A Decision Framework for Engineering Teams
A practical decision framework for when to memoize in React — how to evaluate the cost, benefit, and timing of useMemo, useCallback, and React.memo in team codebases.
React memoization is one of the most consistently misapplied tools in front-end engineering. Teams either ignore it until the app is noticeably slow, or adopt it as a blanket policy and wrap everything regardless of measured impact. Neither approach produces good outcomes. The first leads to preventable performance issues in production; the second leads to codebases that are harder to maintain without meaningfully better performance.
The question "when should we memoize?" deserves a concrete answer. This framework is designed for engineering teams that want a shared, repeatable standard.
The Cost Model of Memoization
Before deciding whether to memoize, understand what memoization costs:
- Memory: The cached value or function stays in memory between renders. For large computed values, this can be significant.
- Comparison overhead: On every render, React compares the current dependency array to the previous one. For
React.memo, it compares every prop. This comparison is not free — it has a cost proportional to the number of dependencies and the complexity of the values compared. - Cognitive overhead: Memoization adds a mental model requirement. Engineers reading the code must understand why each
useMemooruseCallbackexists and what would break if it were removed.
Memoization is justified when the benefit — reduced render time or reduced computation — exceeds all three of these costs. When the computation is fast or the component is cheap to render, it rarely does.
The Decision Framework
Work through these questions in sequence before adding any memoization:
Step 1: Have you profiled the interaction?
If you have not used the React DevTools Profiler to record the slow interaction and confirm which components are expensive or rendering too often, stop. Profile first.
Memoization decisions made without profiling data are guesses. The Profiler shows you which components are actually slow and why they rendered. Without it, you are solving a problem you have not yet found.
Step 2: Is the problem slow computation or too many renders?
The answer changes which tool you reach for.
- Slow computation in a component: Consider
useMemoto cache the expensive result. - A component rendering more than it should: Consider structural fixes first (state colocation, context splitting). If structure cannot fix it, consider
React.memoon the component and stabilizing its props.
Applying useMemo to a component that renders too frequently does not reduce render frequency. Applying React.memo to a component with an expensive computation does not make the computation cheaper — it only prevents re-running it when props are stable.
Step 3: Is the computation genuinely expensive?
For useMemo: a computation that runs in under 1ms is not worth memoizing. Filtering an array of 20 items, formatting a date, concatenating strings — these are fast. The useMemo overhead may match or exceed the computation cost.
Genuinely expensive computations: sorting or filtering arrays of hundreds or thousands of items, performing cryptographic operations, running complex statistical calculations, or transforming large datasets.
If you are unsure, use console.time to measure the computation in isolation, or use the Profiler to see whether removing it from the render makes a visible difference.
Step 4: Are the props actually stable enough for React.memo to work?
React.memo compares props by reference (shallow equality). If any prop is an object, array, or function created inline in the parent, the reference changes on every parent render and React.memo does nothing.
Before adding React.memo to a component, audit every prop it receives:
- Primitives (strings, numbers, booleans): stable by value.
- Objects and arrays: only stable if they are themselves memoized with
useMemo. - Functions: only stable if wrapped with
useCallback.
If the prop audit reveals unstable references, you must stabilize them first. React.memo without stable props is dead code.
Step 5: Is the child component actually slow enough to justify the complexity?
A component that renders in 2ms does not justify React.memo on it, even if it renders more often than ideal. The comparison overhead and the cognitive load of maintaining stable props to that component cost more than the 2ms savings.
The threshold that matters in practice: components that take more than 5-10ms to render on the target device, or components that render dozens of times per user interaction and whose cumulative cost is measurable.
Team-Level Policies That Work
Rather than evaluating each case individually under deadline pressure, establish shared defaults:
Default: no memoization until measured. Write straightforward code. Profile before optimizing. This keeps the codebase readable and puts optimization effort where it matters.
Exception: list row components. Row components in virtualized or long lists are almost always worth wrapping in React.memo. They match the exact pattern the API was designed for: many similar components with stable props.
Exception: context values. Context values that are objects or arrays should be memoized with useMemo by default. This is cheap to do at authoring time and prevents an entire subtree of consumers from re-rendering on unrelated state changes.
Review trigger: new useMemo or useCallback in a PR. Require a comment in the code explaining what was measured and what the result was. This friction is intentional — it prevents cargo-cult memoization from accumulating.
The Compiler Changes the Calculus
React's compiler (previously called React Forget, now in stable release) automatically inserts memoization where it can be statically determined to be safe. For applications running the React compiler, many manual useMemo and useCallback usages become redundant.
Before adding manual memoization to a codebase that uses the compiler, verify that the compiler has not already handled the case. Duplicate memoization — manual on top of compiler-inserted — adds overhead without benefit.
Summary
Memoize when:
- You have profiled and confirmed a specific performance problem.
- The problem is either an expensive computation or a component re-rendering too often with stable props.
- The component is slow enough (measured) to justify the complexity.
- Props are already stable, or you are willing to stabilize them.
Do not memoize when:
- You have not profiled.
- The computation is cheap.
- The child component is fast.
- Props are unstable and you are not fixing that.
If your team wants help establishing engineering standards for a React application at scale, Clixo works with product teams on architecture, performance, and delivery.