In React, a component is wrapped in React.memo. Which re-renders does that actually skip, and which does it not prevent?
answer
- props path only
- parent-driven renders
- state and context still win
- shallow, Object.is per prop
- optimization, never a guarantee
basics
~20 sReact.memo skips a re-render caused by the parent, and only when every new prop is the same as the old one by a shallow comparison. Its own state updates, a context change it reads, or any changed prop still re-render it.
solid answer
~50 s`React.memo(Component)` returns a wrapper that, when the parent re-renders, compares the incoming props with the previous ones prop by prop using `Object.is`. If they all match, React does not call your component function and reuses the previously rendered output, which also spares everything below it. That is the only thing it skips. It does nothing about updates that originate inside the component: a `useState` or `useReducer` update re-renders it, and a context value it reads with `useContext` re-renders it even when props are identical. It also cannot help if a prop is a freshly created object, array or arrow function each render, because those are never equal. And React treats it as an optimization, not a promise — it may re-render a memoized component anyway, so never write logic that depends on the skip happening.
code
javascript · 20 linesimport { memo, useState } from 'react';
const Row = memo(function Row({ label }) {
const [open, setOpen] = useState(false);
return (
<button onClick={() => setOpen(!open)}>
{label}: {open ? 'open' : 'closed'}
</button>
);
});
export default function List() {
const [tick, setTick] = useState(0);
return (
<div>
<button onClick={() => setTick(tick + 1)}>tick {tick}</button>
<Row label="Ada" />
</div>
);
}go deeper
Be ready to say that React.memo compares the incoming props and skips the render when they all match, and that state changes inside the component still re-render it.
Explain the mechanics: a shallow per-prop Object.is comparison on the parent-driven path, the subtree below skipped along with it, and context subscriptions bypassing the check entirely.
Show you have debugged this in production — name inline object, array and arrow props as the reason a memoized component keeps rendering, and read the render reason from the profiler rather than guessing.
Own the framing that memo is an optimization React may ignore, so nothing in the codebase's correctness may depend on a skipped render; steer teams toward structural fixes before adding wrappers.
## What React.memo actually is `React.memo` takes a component and returns a new component. The returned component behaves identically, with one addition: when React is about to re-render it *because its parent re-rendered*, React first compares the new props object with the previous one. The comparison is **shallow** — for each key, React asks `Object.is(prevProps[key], nextProps[key])`. If every key matches, React skips calling your function and reuses the element tree it produced last time. ```jsx const Row = memo(function Row({ label }) { return <li>{label}</li>; }); ``` If the parent re-renders a hundred times and always passes `label="Ada"`, the `Row` function body runs once. Because React never re-rendered `Row`, it also never descends into `Row`'s children, so the whole subtree underneath is skipped too. That subtree skip is usually where the real saving is: memoizing a single `<li>` saves almost nothing, memoizing the root of a heavy panel can save a lot. ## The three things it does not prevent **Its own state.** A `useState` or `useReducer` update inside a memoized component re-renders it, exactly like any other component. `React.memo` sits on the props path only; it has no opinion about internal state. This is the misconception juniors most often carry in — that `memo` means "renders once". **Context.** If the component calls `useContext(SomeContext)` and the provider publishes a new value, the component re-renders even when its props are byte-for-byte the same. Context subscriptions bypass the props comparison entirely. **Props that are new every render.** If the parent writes `<Row style={{ color: 'red' }} onSelect={() => pick(id)} />`, both props are freshly allocated objects on every parent render, so `Object.is` reports them unequal and the bailout never fires. The comparison still runs, so you pay a small cost and get nothing. ## It is an optimization, not a guarantee React reserves the right to re-render a memoized component anyway — the React documentation says so explicitly. Treat a skipped render as a performance win you may or may not get, never as a semantic guarantee. Concretely: never put a side effect in a render body and reason "it will only run when props change", and never use `memo` to keep a component from resetting something. If you need something to survive, hold it in state, a ref, or state moved above the component. ## Where the boundary belongs Because `memo` only intercepts the parent-driven path, it is worth placing where a parent re-renders often but the child's inputs rarely move — a static sidebar under a page that re-renders on every keystroke, a row component inside a long list whose row data is stable, a chart fed by a value that changes once a minute. It is close to worthless on a component that renders a few DOM nodes from primitives, because the comparison costs about as much as the render it avoids. ## What to say in an interview State the mechanism in one sentence — shallow prop comparison on the parent-driven render path — then immediately name the escapes: own state, context, and unstable props. Candidates who can list the escapes are signalling they have actually debugged a memoized component that kept re-rendering, which is the thing the question is really probing.
- If a memoized component re-renders every single time despite unchanged-looking props, what is your first check?Look at what the parent passes. An object literal, array literal, arrow function or JSX child created inline in the parent's render is a new reference each time, so the shallow comparison always reports a difference. Check the props one by one in the DevTools Profiler's render-reason list before assuming anything about React itself.
- Does React.memo do anything for a component that renders because of a context update?No. A `useContext` subscription re-renders the consumer whenever the provider's value changes, and the props comparison never gets a chance to bail out. `memo` only intercepts the parent-render path, so the usual fixes are elsewhere: narrow the context, split it, or stop putting a freshly built object in the provider value.
- Is it safe to rely on React.memo to stop an expensive side effect from running twice?No. React documents memoization as a performance optimization it may ignore, so a skipped render is never guaranteed. Side effects belong in an effect with the right dependencies, or in an event handler — not in a render body whose frequency you are trying to control with `memo`.
React.memo is a doorman who checks whether the delivery is identical to yesterday's and turns it away if so. It has no authority over what the people already inside the room decide to do.
saying these in an interview costs you the question
- Says React.memo makes a component render only once
- Thinks memo blocks the component's own setState updates
- Believes memo compares props deeply
- Assumes memo protects a consumer from context updates
- Treats a skipped render as a guarantee React must honour