Back to blog

Does memo Actually Save Memory?

9 min read
react performance javascript optimization

I once reviewed a codebase where someone had wrapped every component in React.memo, convinced they were reducing memory usage. When we profiled the app, memory consumption had increased by 34%. The renders they were trying to prevent? Most were already cheap enough that the comparison overhead cost more than the render itself.

This is the memo trap, and it starts with the name.

The Name Trap

When React developers first encounter React.memo, a natural assumption forms: this thing saves memory. The word is right there in the name. But memo isn’t short for “memory”, it’s short for memoization, the process of caching the result of a computation so you don’t have to repeat it.

The irony is that memoization, by definition, uses memory to save CPU cycles.

So React.memo doesn’t save memory. In most cases, it costs you some. Understanding this distinction changes how you think about when and why to reach for it.

What React.memo Actually Does

React.memo is a higher-order component that wraps a function component and prevents it from re-rendering unless its props have changed.

const UserCard = React.memo(function UserCard({ name, avatarUrl }) {
  return (
    <div className="card">
      <img src={avatarUrl} alt={name} />
      <h2>{name}</h2>
    </div>
  );
});

Without memo, every time the parent component re-renders, UserCard re-renders too regardless of whether name or avatarUrl changed. With memo, React holds onto the previous props and does a shallow comparison before deciding whether to re-render. If the props are the same, the render is skipped entirely.

That cached copy of the previous props has to live somewhere. It lives in memory.

How Shallow Comparison Works

By default, React.memo uses shallow equality to compare old and new props. This means it checks whether each prop value is the same reference (for objects and arrays) or the same value (for primitives).

Here’s where many developers get tripped up:

// ✅ Skips re-render: "Alice" === "Alice" (same primitive value)
<UserCard name="Alice" avatarUrl="/alice.png" />

// ❌ Re-renders every time: new object created on each render
<UserCard user={{ name: "Alice" }} />

The second case fails because JavaScript objects are compared by reference, not by their contents. Even though both objects contain { name: "Alice" }, they’re different objects in memory:

{ name: "Alice" } === { name: "Alice" }  // false — different references
"Alice" === "Alice"                       // true — same primitive value

This is one of the most common ways memo silently stops working. If you’re passing a new object, array, or function as a prop on every render, the shallow comparison always fails and memo never bails out. You get the memory overhead of caching with none of the render savings.

You can override the comparison logic with a custom comparator:

const UserCard = React.memo(
  function UserCard({ user }) {
    return <div>{user.name}</div>;
  },
  (prevProps, nextProps) => prevProps.user.id === nextProps.user.id
);

Use this carefully. A buggy comparator that returns true too eagerly will cause your component to display stale data.

The Real Memory Cost

Let’s get concrete about what React actually stores when you use memo. I ran a simple benchmark rendering 1,000 list items with and without memoization:

// Without memo
function ListItem({ item }) {
  return <div>{item.name}</div>;
}

// With memo
const MemoizedListItem = React.memo(function ListItem({ item }) {
  return <div>{item.name}</div>;
});

Heap snapshot results:

ScenarioHeap SizeDifference
1,000 items without memo2.1 MBbaseline
1,000 items with memo2.4 MB+14%
10,000 items without memo18.3 MBbaseline
10,000 items with memo21.7 MB+19%

For each memoized component, React retains:

  • The wrapped component reference
  • The previous props object from the last render
  • The previous rendered output (the React element tree)
  • Internal fiber node metadata for comparison

None of this is enormous for a single component, roughly 200-400 bytes of overhead per memoized instance. But it accumulates. A large list with thousands of memoized items each storing their last props and output represents a meaningful heap footprint.

Compare this to a non-memoized component: React renders it, the previous output becomes eligible for garbage collection, and memory is reclaimed. Simpler, more memory-efficient.

useMemo and useCallback: The Same Tradeoff

useMemo and useCallback follow the same pattern at the hook level:

// Caches the filtered list between renders
const filtered = useMemo(
  () => items.filter(item => item.active),
  [items]
);

// Caches the function reference between renders
const handleClick = useCallback(() => {
  onSelect(id);
}, [id, onSelect]);

Both keep a copy of their previous dependency values and their previous return value in memory so they can compare and skip recomputation. The dependency arrays themselves are cached.

This is particularly important with useCallback. A common antipattern is wrapping every callback in useCallback by default:

// ❌ Pointless memoization
function Parent() {
  const handleClick = useCallback(() => {
    console.log('clicked');
  }, []);

  // Child isn't wrapped in memo, so it re-renders anyway
  return <Button onClick={handleClick} />;
}

If the component receiving the callback isn’t wrapped in React.memo, caching the function reference accomplishes nothing. You’ve added memory overhead and complexity without preventing any re-renders.

When memo Is Worth It

memo earns its place in specific scenarios:

Expensive renders. If a component does heavy DOM work, renders complex SVGs, or processes significant data during render, skipping that work matters. Profile your component, if it consistently takes more than 2-3ms to render, memoization may help.

// Worth memoizing — renders a complex chart
const ExpensiveChart = React.memo(function ExpensiveChart({ data }) {
  // Complex SVG generation, 15ms render time
  return <svg>{/* hundreds of path elements */}</svg>;
});

Stable props that change infrequently. A Header component that receives the current user’s name and avatar will re-render every time unrelated parent state changes. memo eliminates that waste cleanly.

Large lists where most items stay unchanged. When rendering hundreds of rows and only a few change between updates, memoizing individual row components lets React skip the unchanged ones. This is one of memo’s strongest use cases.

When profiling confirms a bottleneck. The React DevTools Profiler tells you which components re-render and how long they take. Look for the “Rendered at” timestamps and durations. If a component shows up consistently with expensive renders triggered by unchanged props, that’s your signal.

When memo Is Not Worth It

Components that always receive new props. If props change on every render anyway, memo adds overhead for a comparison that always fails.

Cheap, simple components. A component that renders in microseconds doesn’t need memoization. The comparison cost can exceed the render cost:

// ❌ The comparison costs more than the render
const Dot = React.memo(({ color }) => (
  <span style={{ backgroundColor: color, width: 8, height: 8 }} />
));

Before profiling. Premature optimization leads to complex, harder-to-maintain code without measurable gains. Don’t guess, measure.

How to Actually Profile This

Here’s how to identify memoization opportunities using React DevTools:

  1. Open React DevTools → Profiler tab
  2. Click “Start profiling” and interact with your app
  3. Stop profiling and examine the flamegraph
  4. Look for components that:
    • Re-render frequently (many commits)
    • Take >2ms per render
    • Have props that didn’t actually change (check “Why did this render?”)

Components meeting all three criteria are memoization candidates. Components meeting only one or two probably aren’t.

A Decision Framework

Here’s a practical framework for deciding whether to use memo:

Should I use React.memo?

1. Is the component re-rendering when its props haven't changed?
   No  → Don't use memo
   Yes → Continue

2. Does the render take more than 1-2ms?
   No  → Probably don't use memo
   Yes → Continue

3. Are the props stable references (primitives, or objects/functions
   created outside render or wrapped in useMemo/useCallback)?
   No  → Fix the props first, then reconsider memo
   Yes → Use memo

What About React Compiler?

If you’re already using React 19 (or 17, 18 also supported), you should know about React Compiler (formerly React Forget). It automatically memoizes components and values during compilation, making manual memo, useMemo, and useCallback largely unnecessary.

// React Compiler does this automatically
function UserCard({ name, avatarUrl }) {
  return (
    <div className="card">
      <img src={avatarUrl} alt={name} />
      <h2>{name}</h2>
    </div>
  );
}
// No manual memo needed — compiler handles it

If your project uses React Compiler, you can remove most manual memoization. The compiler makes smarter decisions about what to cache based on static analysis of your code.

Check if React Compiler is enabled in your project before reaching for manual optimization.

A Note on Server-Side Rendering

During SSR, memo has no effect, components render once to generate HTML, and there’s no “previous render” to compare against. The memoization overhead only applies on the client after hydration.

This means memo is purely a client-side optimization. It won’t speed up your server renders or reduce Time to First Byte.

The Right Mental Model

Think of React.memo not as a memory-saving tool but as a render gate, a checkpoint where React asks “did the inputs change?” before deciding whether to do work.

To answer that question, React needs to remember what the inputs were last time. That’s the memory cost.

This mental model makes it easier to reason about when the gate is worth installing. You’re adding memory and comparison logic upfront to potentially avoid render work later. That trade is sometimes excellent and sometimes not worth it.

Summary

QuestionAnswer
Does memo save memory?No, it uses more memory to cache props and output
What does memo save?CPU cycles by skipping unnecessary re-renders
When does memo help?Expensive renders (>2ms) with stable, infrequently-changing props
When does memo hurt?Cheap renders, always-changing props, unstable object/function props
What about React Compiler?It auto-memoizes, check if it’s enabled before manual optimization
How should you decide?Profile first, optimize second

Now you know the truth: it’s a performance optimization that costs a little memory to potentially save a lot of rendering work. Use it deliberately, and measure the results.


Abisoye Alli-Balogun

Written by Abisoye Alli-Balogun

Full Stack Product Engineer building scalable distributed systems and high-performance applications. Passionate about microservices, cloud architecture, and creating delightful user experiences.

Enjoyed this post? Check out more articles on my blog.

View all posts

Comments