React Interview Questions: Performance, Rendering and Memoization

Reviewed by Mark Dickie · Last updated

7 questions on this page you can answer and see marked on the spot. Go to the first one

React is a JavaScript library for building user interfaces that re-runs a component whenever its state or props change. Performance questions check whether you can explain which work React repeats on each render, and whether that work is worth stopping. Interviewers usually begin with the render cycle, then give you a slow screen and ask which of memo, keys, lazy loading or deferred updates fixes it. Name the measured cost first, because a re-render alone is rarely the problem.

Which React performance topics come up in interviews?

TopicWhat interviewers askWhat a strong answer names
React.memoWhy a child still re-renders when its parent doesShallow prop comparison, and how an inline object or arrow function defeats it
useMemo and useCallbackWhen memoizing a value or function helpsReferential equality for dependency arrays, and that memoizing cheap work adds cost
key propWhy a list reorders slowly or loses input stateIndex keys reuse the wrong DOM node after insertions, stable IDs keep it
useTransition and useDeferredValueHow to keep typing responsive during a heavy updateMarks the update as non-urgent so input events render first
React.lazy and SuspenseHow to cut the initial bundleSplits a route or panel into its own chunk, loaded on first render
React ProfilerHow you would find the slow componentRecords commit timings per component, showing why each one rendered

How do you find a slow component in React?

  1. Open React DevTools, switch to the Profiler tab, and start a recording.
  2. Perform the slow interaction, such as typing into a filter box with a few thousand rows.
  3. Stop the recording and sort components by commit duration to find the expensive ones.
  4. Check the "Why did this render?" setting to see whether props, state or context changed.
  5. Apply one fix, such as memoizing the row component, then record again and compare the commit times.

Key facts

  • Tarmac has 25 React interview questions on this topic, 10 of them on this page, at difficulty 3–5 of 5.
  • Tarmac tracked 2,281 job postings asking for React in October 2026.
  • Roles asking for React advertise a median base salary of £75,000, across 588 job postings as of October 2026.
  • Tarmac last reviewed these React interview questions on 2 November 2026.

At a glance

Questions10 shown · 25 in the bank
Difficulty3–5 of 5
FormatsMultiple choice, Multiple answer, True / false, Short answer, Find the bug, Flashcard, Code output

What you'll review

  1. react memo
  2. code splitting
  3. rerender causes
  4. react compiler

Practice questions

Try one before you open the answer. Pick an option and press Check; it's marked on the spot.

What does wrapping a component in React.memo do?#

Options

Show answer

React.memo skips re-rendering the component when its props are shallowly equal to the previous render's props. It compares the incoming props and bails out of the render when nothing changed. It does not memoize internal state, and it has no effect on re-renders triggered by the component's own useState or context — those still re-render it normally.

Why:

React.memo does a shallow comparison of props and bails out of re-rendering when they haven't changed. It does not affect re-renders caused by the component's own state or context, and it does not cache state across unmounts.

How do React.lazy and <Suspense> work together for code splitting?#

Options

Show answer

React.lazy defers loading a component's code until it actually renders, and <Suspense fallback={…}> shows the fallback while that chunk downloads. Calling React.lazy(() => import('./X')) returns a component whose bundle is fetched on first render; until the dynamic import resolves, the nearest Suspense boundary displays its fallback. A lazy component must be rendered inside such a boundary.

Why:

React.lazy(() => import('./X')) creates a component whose code is fetched on first render; while that dynamic import is pending the nearest <Suspense> boundary renders its fallback. A lazy component must be rendered inside a Suspense boundary.

Which of these cause a React component to re-render?#

Options

Pick every one that applies.

Show answer

Three things trigger a re-render: calling the component's state setter with a new value, receiving new props, and its parent re-rendering (unless the component is memoized). Mutating a plain, non-state variable does not — React never observes that change, so it schedules no render. Only state, props, and parent renders flow through React's update mechanism.

Why:

A component re-renders when its state changes, its props change, or its parent re-renders (unless memoized). Mutating a normal variable does not schedule a render — React never sees the change.

Calling a state setter with a value that is Object.is-equal to the current state always triggers a re-render.#

Options

Show answer

False. When the new state is Object.is-equal to the current state, React bails out — it may run the component once more before bailing, but it won't re-render children or commit any changes. Setting the same primitive value is effectively a no-op, so it does not always trigger a visible re-render.

Why:

React bails out when the new state is Object.is-equal to the current state — it may still re-render the component once before bailing, but it will not re-render children or commit changes. Setting the same primitive value is effectively a no-op.

What triggers a React function component to re-render?#

Show answer

A component re-renders when its own state changes (a setter is called with a different value), when it receives new props, when a context value it consumes changes, or when its parent re-renders. Mutating a non-state variable does not trigger a re-render because React is unaware of the change.

Why:

The triggers are state change, new props, a consumed context value changing, or a parent rendering (unless the child is memoized and its props are unchanged). Plain variable mutation is invisible to React.

Chart is wrapped in React.memo and data never changes here. Switching tabs still re-renders Chart every time. Which line is the source of the bug?#

const Chart = React.memo(function Chart({ data, colors }) {
  return <SvgChart data={data} colors={colors} />;
});

function Dashboard({ data }) {
  const [tab, setTab] = useState('overview');
  return (
    <div>
      <TabBar tab={tab} onChange={setTab} />
      <Chart data={data} colors={['#4f46e5', '#16a34a', '#dc2626']} />
    </div>
  );
}
Show answer

The bug is on line 10.

Why:

colors={['#4f46e5', '#16a34a', '#dc2626']} allocates a brand-new array literal on every render of Dashboard, even though its contents never change. React.memo's shallow comparison checks colors with Object.is, and two distinct array objects are never Object.is-equal no matter what they contain, so Chart's props always read as changed and memo re-renders it on every Dashboard render — including every tab switch, which has nothing to do with data or colors. Fix: hoist the array to a module-level constant (created once, outside the component) or wrap it in useMemo(() => [...], []) so the same reference is reused across renders.

The React Compiler can safely auto-memoize a component only if that component follows the Rules of React (pure render, no mutation of props/state during render).#

Options

Show answer

True. The compiler can safely auto-memoize a component only when it follows the Rules of React — pure render, no mutation of props or state during render. Those guarantees are what let it cache values and reuse them; a hidden violation could make memoization serve stale output. When it can statically detect a violation it skips the component, leaving it correct but un-optimized. The ESLint rules exist to surface such violations beforehand.

Why:

The compiler's correctness rests on the assumption that rendering is pure and inputs aren't mutated mid-render — that's what lets it cache values and reuse them. When it can statically detect a violation it skips the component (leaving it un-optimized but correct); when a violation is hidden from static analysis, memoizing could serve stale output, so following the Rules of React is the prerequisite for the compiler to memoize safely. The eslint-plugin-react-hooks/React Compiler ESLint rules exist precisely to surface those violations before the compiler runs.

In one breath: what is the React Compiler, and what does it change about how you write components?#

Show answer

It's an opt-in build-time tool that automatically memoizes your components and their derived values, so you no longer sprinkle useMemo/useCallback/React.memo for routine re-render avoidance. You write straightforward components that follow the Rules of React (pure render, no mutation during render), and the compiler inserts the memoization. It only optimizes code it can prove is safe — anything it can't is left untouched, never miscompiled.

Why:

The shift is from manual, defensive memoization to writing clean components and letting the compiler optimize. The Rules of React aren't new etiquette — they're the contract the compiler depends on, which is why the ESLint rules are part of the workflow.

Starting from mount, the user clicks "bump other" three times and nothing else (count stays 0 throughout). In total, how many times does Child's function body run?#

let childRenders = 0;

const Child = React.memo(function Child({ count, onClick }) {
  childRenders += 1;
  return <button onClick={onClick}>{count}</button>;
});

function Parent() {
  const [count, setCount] = useState(0);
  const [other, setOther] = useState(0);
  const onClick = () => setCount(count + 1);
  return (
    <div>
      <button onClick={() => setOther(other + 1)}>bump other</button>
      <Child count={count} onClick={onClick} />
    </div>
  );
}

Options

Show answer
4 — one mount plus one re-render per click, because `onClick` is a new function reference every time Parent renders
Why:

Each click on "bump other" updates other, re-rendering Parent, which redefines onClick as a brand-new arrow function every time. React.memo compares every prop with Object.is, and onClick is a different function reference on each Parent render even though count stays 0, so the shallow comparison finds a changed prop and Child re-renders. That's 1 mount + 3 re-renders (one per click) = 4 total. If onClick were wrapped in useCallback(() => setCount(count + 1), [count]) (stable across renders where count doesn't change), the three clicks would produce zero additional Child renders, since both count and onClick would be Object.is-equal to their previous values.

Your project has the React Compiler enabled, so most components are auto-memoized. In which situations might you STILL reach for manual useMemo/useCallback (or otherwise can't rely on the compiler)?#

Options

Pick every one that applies.

Show answer

Manual memoization still helps in three situations: when a value or callback crosses out of React — into a non-React library, a useEffect dependency you need referentially stable, or an external subscription; when the compiler skips a component because it can't prove the code follows the Rules of React; and when you want to guarantee a genuinely expensive computation is cached rather than trust the compiler's heuristics. It is false that you always need it everywhere — the compiler auto-memoizes ordinary components without a React.memo wrapper.

Why:

The compiler removes the routine memoization burden, but manual memoization still has a role at the edges. When a value leaves React's world — feeding a non-React library, anchoring a useEffect dependency, or driving an external store — you may want an explicit stability guarantee rather than relying on compiled output. The compiler only optimizes code it can prove is safe; a component that breaks the Rules of React (mutating props/state during render, reading refs in render) is skipped, so its children re-render as before. For a known-expensive computation you may still memoize explicitly to guarantee caching rather than depend on the compiler's cost heuristics. The claim that the compiler memoizes only components wrapped in React.memo is false: the compiler memoizes ordinary components automatically and does not require a React.memo wrapper.

Sources

The official documentation these questions are checked against:

Related interview questions

Job market

See react salaries and hiring demand from live job postings.

The other 15 questions

This page shows 10 and marks what you pick. That's as far as a page can go. A free account opens the other 15 and keeps every answer. What you miss comes back until it's right: after a day, then at longer gaps.

Start with this topic

Free · the whole bank · 100 marked answers per 30 days · written feedback on the paid plan

Interview booked? Practise the job ad instead

What moved, monthly

One email a month when the bulletin comes out: what moved in the markets we track, and the new question topics we published. Confirm your address to join. Unsubscribe any time.