Skip to content

React.memo Interview Questions & Answers

14 questions Updated 2026-06-24 Share:

React.memo interview questions — when to memoize components, shallow equality, custom comparators, pitfalls, and when memo actually hurts performance.

Read the in-depth guideReact.memo — A Complete Guide with Examples(opens in new tab)
14 of 14

React.memo is a higher-order component that wraps a function component and skips re-rendering it when its props are shallowly equal to the previous render's props.

const Button = React.memo(function Button({ label, onClick }) {
  console.log('render')
  return <button onClick={onClick}>{label}</button>
})

// Parent re-renders with same label and onClick reference:
// → Button's console.log does NOT fire

Under the hood, before calling your component function React compares each old prop to each new prop with Object.is. If all are equal, it reuses the previous render output.

React.memo only applies to function components. The class-component equivalent is PureComponent (or shouldComponentUpdate).

Rule of thumb: React.memo is a performance hint, not a correctness guarantee — React may still re-render the component in some cases (e.g., concurrent rendering). Never rely on it to prevent side effects from firing.

Shallow equality compares values one level deep using Object.is: primitives are compared by value; objects and functions are compared by reference.

Object.is(1, 1)           // true  — same primitive
Object.is('a', 'a')       // true  — same primitive
Object.is([], [])         // false — different object references
Object.is({ x: 1 }, { x: 1 }) // false — different object references

This is why inline objects and functions as props break memoisation:

// BAD: new object on every render → memo always re-renders
<Chart options={{ color: 'red' }} />

// GOOD: stable reference, memo works
const OPTIONS = { color: 'red' }
<Chart options={OPTIONS} />

// GOOD: memoised object
const options = useMemo(() => ({ color }), [color])
<Chart options={options} />

Rule of thumb: Pass primitives when possible; stabilise object and function props with useMemo / useCallback when using React.memo.

React.memo pays off when all three conditions hold:

  1. The component renders frequently (its parent re-renders often).
  2. The component renders expensively (large subtree, heavy computation).
  3. Its props are stable (primitives or memoised references).
// Worth memoising: renders on every keystroke, large list
const ResultList = React.memo(function ResultList({ items }) {
  return (
    <ul>
      {items.map(item => <li key={item.id}>{item.name}</li>)}
    </ul>
  )
})

// Not worth memoising: renders rarely, trivially cheap
const PageTitle = React.memo(function PageTitle({ text }) {
  return <h1>{text}</h1>
})

The overhead of React.memo itself (the shallow comparison on every parent render) can exceed the savings if the component is cheap or its props are unstable objects.

Rule of thumb: Profile first. Reach for React.memo only when the Profiler shows the component actually re-renders unnecessarily and the re-render is measurably expensive.

Pass a second argument to React.memo: a function that receives the previous and next props and returns true if the component should skip re-rendering (i.e., they are "equal enough").

const Chart = React.memo(
  function Chart({ data, width }) {
    return <canvas ref={drawChart(data, width)} />
  },
  (prevProps, nextProps) => {
    // Skip re-render if only the data length changed, not actual values
    return (
      prevProps.width === nextProps.width &&
      prevProps.data.length === nextProps.data.length &&
      prevProps.data.every((v, i) => v === nextProps.data[i])
    )
  }
)

Important: returning true means do NOT re-render (the opposite of shouldComponentUpdate, which returns true to allow a re-render). This is a common source of confusion.

Rule of thumb: Custom comparators add complexity and can silently suppress needed updates if written incorrectly. Use them only when the default shallow comparison is measurably too eager.

Each render creates a new function object, so the reference changes every time, causing React.memo's shallow comparison to always return "not equal" and re-render anyway.

function Parent() {
  const [count, setCount] = useState(0)

  // New function reference on every Parent render → memo useless
  return <Child onClick={() => console.log('click')} />
}

const Child = React.memo(function Child({ onClick }) {
  console.log('Child render') // still fires every time
  return <button onClick={onClick}>Click</button>
})

Fix with useCallback to stabilise the reference:

function Parent() {
  const [count, setCount] = useState(0)

  const handleClick = useCallback(() => {
    console.log('click')
  }, []) // stable reference — same function object across renders

  return <Child onClick={handleClick} />
}

Rule of thumb: React.memo and useCallback are best friends — you almost always need both: memo to skip the child render, and useCallback to prevent the function prop from busting the memo.

Both perform a shallow prop comparison to avoid unnecessary re-renders, but they apply to different component types.

React.memo PureComponent
Works with Function components Class components
Custom equality 2nd argument comparator Override shouldComponentUpdate
Checks state No (state is inside the hook, not a prop) Yes — shallow-compares both props and state
// Function component
const Foo = React.memo(function Foo(props) { … })

// Class component
class Foo extends React.PureComponent {
  render() { … }
}

PureComponent also shallow-compares this.state, which React.memo has no equivalent for (state is internal to hooks and React itself handles bailing out when the state reference doesn't change).

Rule of thumb: In modern React (function components), React.memo is the standard tool. PureComponent is for legacy class components.

No. React.memo only skips re-renders caused by changed props. If a memoised component subscribes to a context (via useContext) and the context value changes, the component will re-render regardless of whether its props changed.

const ThemeContext = createContext('light')

const Card = React.memo(function Card({ title }) {
  const theme = useContext(ThemeContext) // subscribed to context
  return <div className={theme}>{title}</div>
})

// Parent provides a new context value:
// Card re-renders even though `title` prop didn't change

Mitigation strategies:

  • Split context into multiple smaller contexts so unrelated consumers don't re-render.
  • Use useMemo inside the component to memoize the expensive derived output, even if render is called.
  • Use a state-management library (Zustand, Jotai) that uses selectors to subscribe only to the slice of state a component needs.

Rule of thumb: React.memo and context subscriptions are orthogonal — to limit context-driven re-renders you need to narrow the context or use selectors, not memo.

When you write <Wrapper><Child /></Wrapper>, React creates a new element object for <Child /> on every render of the parent where the JSX lives. Because element objects are new references each time, React.memo's shallow comparison sees a changed children prop and re-renders.

function Parent() {
  const [count, setCount] = useState(0)

  // New <div>Hello</div> element object created every render
  return <Wrapper>{<div>Hello</div>}</Wrapper>
}

const Wrapper = React.memo(function Wrapper({ children }) {
  console.log('Wrapper render') // fires on every Parent re-render!
  return <section>{children}</section>
})

Fixes:

  1. Move the children JSX outside the re-rendering parent (lift it up).
  2. Wrap children in useMemo if they depend on parent state.
  3. Restructure so Wrapper doesn't need to be memoised.

Rule of thumb: Memoising components that accept children is often counter-productive — consider memoising the children themselves instead, or rethinking the component hierarchy.

Avoid React.memo when:

  1. Props change on every render anyway — memo runs the comparison but still re-renders, adding overhead for zero gain.
  2. The component is trivially cheap — shallow-comparing many props can cost more than just running the component.
  3. Props include non-stabilised objects or functions — memo is immediately defeated and the comparison is wasted work.
  4. You haven't profiled — premature memoisation clutters the code and makes it harder to refactor.
// No point memoising — title is a new string computed in parent every render
const Title = React.memo(({ title }) => <h1>{title}</h1>)

function Parent({ user }) {
  return <Title title={`Hello, ${user.name}!`} /> // new string each render
}

Rule of thumb: Reach for React.memo only after the Profiler confirms a component re-renders unnecessarily AND that the re-render is expensive. Default to no memoisation.

When you wrap an anonymous function, React DevTools shows the component as Memo or Anonymous, making the component tree hard to read. Two ways to preserve the name:

// Option 1: named function expression (recommended)
const Button = React.memo(function Button({ label }) {
  return <button>{label}</button>
})
// DevTools shows: Memo(Button)

// Option 2: set displayName explicitly
const Button = React.memo(({ label }) => <button>{label}</button>)
Button.displayName = 'Button'

// Option 3: extract and export the inner component
function ButtonBase({ label }) { return <button>{label}</button> }
export const Button = React.memo(ButtonBase)

Rule of thumb: Always use a named function expression as the first argument to React.memo — it's the simplest and least error-prone way to keep DevTools readable.

Wrap with both — the order is React.memo(React.forwardRef(...)). forwardRef should be the inner wrapper because it transforms the (props, ref) signature; memo wraps the result.

import { forwardRef, memo } from 'react'

const FancyInput = memo(
  forwardRef(function FancyInput({ label }, ref) {
    return (
      <label>
        {label}
        <input ref={ref} />
      </label>
    )
  })
)

// Usage:
const inputRef = useRef()
<FancyInput ref={inputRef} label="Name" />
inputRef.current.focus()

DevTools will show: Memo(ForwardRef(FancyInput)).

Rule of thumb: Compose in the order memo(forwardRef(fn)) — memo on the outside handles the prop comparison; forwardRef on the inside handles the ref plumbing.

shouldComponentUpdate(nextProps, nextState) is the class-component lifecycle that lets you control whether a component re-renders. Returning false skips the render; returning true allows it.

React.memo is the functional-component analogue that handles the same optimisation for props only (it has no access to internal state, which React manages automatically).

// Class component
class List extends React.Component {
  shouldComponentUpdate(nextProps) {
    return nextProps.items !== this.props.items // custom check
  }
  render() { … }
}

// Equivalent with function component + memo
const List = React.memo(
  function List({ items }) { … },
  (prev, next) => prev.items === next.items // same semantics, inverted return
)

Key difference: shouldComponentUpdate returning false skips the update; memo's comparator returning true skips the update (inverted meaning).

Rule of thumb: In new code, prefer function components with React.memo. Use shouldComponentUpdate only when maintaining or extending legacy class components.

When a list contains many items and the parent state changes frequently (e.g., a filter input), each item component gets re-rendered on every keystroke even if its own data hasn't changed. Memoising the item component keeps only the changed item rendering.

// Without memo: all 1000 items re-render on every keystroke
function ItemRow({ item }) {
  return <tr><td>{item.name}</td><td>{item.price}</td></tr>
}

// With memo: only items whose props changed re-render
const ItemRow = React.memo(function ItemRow({ item }) {
  return <tr><td>{item.name}</td><td>{item.price}</td></tr>
})

// Parent
function Table({ rows }) {
  return (
    <tbody>
      {rows.map(row => <ItemRow key={row.id} item={row} />)}
    </tbody>
  )
}

For this to work, rows elements must be stable references — if the parent rebuilds the array on every render, each item prop is a new object and memo is defeated. Stabilise with useMemo.

Rule of thumb: For large lists (50+ items) where individual items are pure, React.memo on the row component is one of the highest-ROI optimisations you can make.

On every parent render, React must:

  1. Look up the previous props object for the memoised child.
  2. Run Object.is on every prop pair (or your custom comparator).
  3. Either skip the child render (if equal) or proceed (if not).

For a component with many props or complex prop objects, step 2 can be non-trivial — especially if the comparator does deep equality or array iteration.

// 20 props, comparator runs 20 Object.is calls on every parent render
const HeavyCard = React.memo(function HeavyCard({
  id, title, description, imageUrl, price, rating,
  inStock, category, tags, author, createdAt, updatedAt,
  onClick, onHover, onFocus, isSelected, isFeatured,
  discount, currency, locale
}) { … })

If the child renders in <1 ms and the parent re-renders infrequently, the memo overhead (prop comparison) may cost more than the render it prevents.

Rule of thumb: Memoize components with few, stable-typed props. For components with many props, the comparison cost can rival the render cost — measure before committing.

More ways to practice

The self-quiz is live. Get notified when mock interviews and new question packs drop.

or
Join our WhatsApp Channel