Why Premature Memoization Can Make React Slower

Memoizing before you measure adds comparison work and complexity. Learn when memo, useMemo, and useCallback help and when they make React slower.

6 min read

Premature memoization means wrapping components and values in memo, useMemo, or useCallback before you have measured a real slowdown. The extra comparison work can cost more than the work it is supposed to save. It also makes the component harder to read, because every cached value now carries a dependency array to keep in sync.

Memoization is not free

memo compares props with Object.is on every render. useMemo and useCallback compare their dependency arrays the same way. That check is cheap per item but never zero, and it runs even when the component would have rendered quickly anyway.

App.jsxApp.jsx
import { memo } from "react";
 
const Row = memo(function Row({ label }) {
  return <li>{label}</li>;
});

A Row that returns a single list item is already fast. Adding memo means React compares label on every render before deciding whether to render. For a dozen items that bookkeeping is invisible, but across hundreds of memoized leaves it adds up while buying nothing.

Cached values also stay in memory until React discards them, so wrapping a large list or tree keeps that extra copy alive between renders.

It can be slower, not faster

When a memoized component receives a new prop on every render, the comparison always fails and the component renders anyway. The wrapper adds a full prop comparison on top of the render, so the total cost is slightly higher than no memo at all.

Imagine a component that renders in under a millisecond. memo still walks every prop with Object.is, so the comparison and the render cost about the same, and you have paid twice for no savings.

The same applies to useMemo and useCallback. A value or function whose dependencies change every render is recomputed anyway, and the Hook only adds the dependency comparison on top.

Custom comparisons can be much worse

A custom comparison function can easily cost more than the render it is trying to skip.

App.jsxApp.jsx
import { memo } from "react";
 
const Chart = memo(function Chart({ dataPoints }) {
  return <div>chart</div>;
}, arePropsEqual);
 
function arePropsEqual(oldProps, newProps) {
  return (
    JSON.stringify(oldProps.dataPoints) ===
    JSON.stringify(newProps.dataPoints)
  );
}

Serializing large arrays to compare them can take longer than re-rendering the chart itself. The official docs recommend measuring a custom comparison before trusting it, because deep equality checks can freeze the app if the data structure grows.

It also costs readability

Every memo, useMemo, and useCallback adds a dependency array that must stay correct as the code changes. A stale array either breaks memoization silently or, in a custom comparison, closes over old values. Teams that memoize by habit spend review time checking arrays that never should have existed.

It hides the real problem

Premature memoization often patches a structural issue. Lifting state too high, storing derived values, or running work in an Effect causes re-renders that memo only hides. Fixing the source removes the re-render entirely and keeps the component tree simple.

A profile of the same app before and after the structural fix usually shows the difference, while memo alone leaves the underlying re-renders in place.

When memoization earns its place

  • Measure with the Profiler and confirm a re-render is the bottleneck.
  • Wrap only the component that renders often with unchanged props.
  • Keep the props stable so the comparison can succeed.

The full measuring workflow is in how to find unnecessary re-renders with React Profiler, and the memo mechanics are in React memo explained. Applied only after a measurement, memoization stops being premature and starts paying for itself. Measure, wrap, then remove the wrapper if the commit duration does not improve.

The compiler removes the tradeoff

React Compiler applies memoization automatically at build time, so codebases that enable it can usually skip the manual wrappers and their comparison overhead altogether. When it is on, the compiler handles memoization without the guessing, and manual memo becomes mostly an escape hatch for places that need precise control.

Rune AI

Rune AI

Key Insights

  • Memoization is not free: it compares props and dependencies.
  • memo buys nothing when props change on every render.
  • Custom deep comparisons can cost more than rendering.
  • Measure with the Profiler before wrapping anything.
  • React Compiler applies memoization automatically.
RunePowered by Rune AI

Frequently Asked Questions

Does memo make a cheap component faster?

No. memo compares props on every render, and for a cheap component the comparison costs about as much as rendering. It only helps when the render is expensive and the props usually stay the same.

When should I add memoization?

After measuring. Confirm a component re-renders often with unchanged props and that its render is slow, then wrap only that component.

Conclusion

Memoization is a tool with a cost. Comparing props and dependencies takes time, and when the work is cheap or the props always change, the wrapper only adds overhead. Measure first, then memoize the specific component the Profiler proves is slow.