React.memo, What Is It Actually Doing?
Muhammad Athar

If you've read Make Use of useMemo Like You Mean It and Did You Know useCallback Can Actually Do This? you've already seen React.memo mentioned as the component-level companion to those hooks.
In Alex Chen's How useDeferredValue and useTransition Work there's this line right in the middle of the live search widget:
And a note that says: "without this, ResultsList re-renders every time the parent renders, regardless of whether query changed."
That's the whole story of React.memo in two sentences. But let's make it completely concrete — because the gap between understanding it in theory and knowing when to actually use it is where most developers get stuck.
Why components re-render when you don't expect them to
Before React.memo makes sense, one thing needs to be clear: React re-renders a child component every time its parent re-renders — even if the child's props didn't change.
This surprises a lot of developers because it feels wrong. Surely React is smart enough to skip a render if nothing changed?
It isn't — by default. React's default behaviour is: parent re-renders → all children re-render. It's a deliberate choice. Re-rendering is cheap most of the time, and checking whether props changed before every render would itself have a cost. React leaves the optimisation to you.
Here's the behaviour in plain code:
Click the button ten times. Child renders eleven times. The name prop never changed. React rendered it anyway.
For a simple component like this one, that's completely fine — rendering <p>Hello, Alex</p> eleven times costs nothing noticeable. But if Child is expensive — a large list, a complex layout, a heavy computation — those extra renders add up.
What React.memo does
React.memo wraps a component and tells React: only re-render this if its props actually changed.
Now click the button ten times. Child renders once. React compares the props before and after the parent re-render, sees name is still "Alex", and skips the render entirely.
The comparison React performs is a shallow equality check — the same logic as === for each prop. For primitive values like strings, numbers, and booleans, this works exactly as you'd expect. For objects, arrays, and functions, it compares references — which is where React.memo starts to feel unreliable without useCallback and useMemo alongside it.
The reference trap
Here's the scenario where React.memo appears to do nothing.
Click the button. MemoizedChild still re-renders on every click. The memo wrapper is doing nothing useful here.
Why? Because user is a new object on every render — different reference, even though the values inside are identical. Same for handleClick — a brand new function on every render. React.memo's shallow comparison sees new references and decides the props changed.
This is exactly why useCallback and useMemo exist alongside React.memo. They stabilise the references so the comparison actually works:
Now MemoizedChild skips the render on every click. The three hooks form a team: React.memo decides whether to re-render, useMemo stabilises object props, useCallback stabilises function props.
Building something real: a notification feed
Here's a notification feed where individual notification cards are memoized. The feed has a header with a live counter that updates every second — without React.memo, every tick would re-render every notification card. With it, only genuinely changed cards re-render.
Open the console and watch. The header re-renders every second — tick changes every second, which re-renders NotificationFeed. But NotificationCard components only log when their specific notification object changes — which only happens when you click "Mark read" on that card.
Without React.memo, all four cards would re-render every second. With it, React skips three of the four on every tick. The useCallback on handleMarkRead is what makes it work — without it, a new function reference on every render would defeat the memo comparison.
The custom comparison function
By default React.memo does a shallow comparison of all props. You can override this with a second argument — a function that returns true if the component should skip the re-render (the props are considered equal) and false if it should re-render.
This is rarely necessary — the default shallow comparison handles most cases. Reach for a custom comparator when you have a specific field in a large object that drives re-renders and you want precise control.
When React.memo is not worth it
Just like useMemo and useCallback, memoization has overhead. React stores the previous props, runs the comparison on every parent render, and skips or proceeds based on the result. For components that are cheap to render, this overhead can exceed the savings.
React.memo is worth it when:
- The component renders a large list, does heavy computation, or has expensive child trees
- The parent re-renders frequently for reasons unrelated to this component's props
- You have confirmed a performance issue — not just suspected one
It is not worth it for:
- Simple components that render in under a millisecond
- Components whose props change on almost every parent render anyway
- Every component by default "just in case"
Profile first. The React DevTools Profiler shows exactly which components are re-rendering and how long each render takes. Let that data drive your decisions.
The trio to remember
React.memo, useCallback, and useMemo are most useful as a team:
React.memo— skips re-rendering a component when props haven't changeduseCallback— stabilises function props so memo's comparison worksuseMemo— stabilises object and array props so memo's comparison works
Use one without the others and you'll often find the optimisation does nothing. Use all three together where they're genuinely needed and re-renders become surgical — only the components that actually need to update do.
Where this leads
React.memo with forwardRef — when a memoized component also needs to forward a ref to a DOM element, you wrap in both. The order matters: memo(forwardRef(...)). This appears in component libraries where parent components hold refs to memoized children.
useDeferredValue and useTransition — the concurrent rendering tools Alex covers in How useDeferredValue and useTransition Work. Where React.memo prevents unnecessary re-renders, these hooks manage the priority of necessary ones. They work at different layers of the same performance problem.
React.memo in a design system — when building a shared component library, wrapping every component in memo is a common pattern. The consumers of the library don't control when their parents re-render — memo ensures library components don't pay for that unpredictability.
This article was prompted by the React Foundations series — particularly How useDeferredValue and useTransition Work by Alex Chen, where React.memo appears in the live search widget without a full explanation. If the notification feed above sparked an idea, the natural next read is Why React.memo Doesn't Always Help — and When It Does by Alex Chen for the deeper performance analysis.
Muhammad Athar is the founder and engineer behind DesignDev.io. He writes the "From the Builder" series — concept explainers triggered by the site's own articles, always grounded in something you can actually build.
Photo by Alexander Grey on Unsplash

