ReactPerformance Optimizationintermediate
Updated:

React Performance Optimization Interview Questions and Answers

6 min read

The performance questions React interviews ask — finding wasted re-renders, memoization that helps, code splitting, virtualization and concurrent features.

TL;DR – Quick Answer

React performance interviews test whether you can find and fix the real bottleneck rather than sprinkle memoization everywhere. Core topics: what causes wasted re-renders, when React.memo, useMemo and useCallback genuinely help, colocating state, code splitting to shrink the bundle, virtualizing long lists, and concurrent features like useTransition and useDeferredValue. The strongest answers start with 'measure first' and target the profiled hot path.

On This Page

Performance is where React interviews reward discipline over reflex. The weak candidate memoizes everything; the strong one measures, finds the one hot path, and fixes it. The questions below cover what actually causes slow React — wasted re-renders, big bundles, huge lists — and the right tool for each, including the concurrent features that keep the UI responsive under load. Lead every answer with "measure first."

How to answer performance questions

Open with process: measure with the Profiler and web vitals, identify the biggest lever, fix that, then re-measure. Most re-renders are cheap and correct, so the goal is not "zero re-renders" but "no expensive wasted work on the hot path." When you name a tool — React.memo, useMemo, code splitting, virtualization — say the specific problem it solves and its cost. Interviewers are listening for judgement, not a list of APIs.

Q1. What causes a component to re-render, and when is that a problem?

A component re-renders when its own state changes, its parent re-renders, or a context it consumes changes. That is normal and usually cheap. It becomes a problem only when the render is expensive (heavy computation, large subtree) and it happens unnecessarily — for example, updating top-level state on every keystroke cascading through a big tree.

The mature framing: re-rendering is not the enemy; wasted expensive re-rendering is. Optimising cheap renders adds complexity for no user-visible gain.

Interview note: Trap: "does a re-render update the DOM?" Not necessarily — React diffs the new output against the old and touches the DOM only where they differ. The render function running is cheaper than a DOM mutation.

Q2. How do you find performance problems instead of guessing?

Use the React DevTools Profiler to record interactions and see which components render, how often, and how long each takes; use the browser performance panel and Core Web Vitals (LCP, INP) for load and interaction latency. Let the data pick the target — the slow part is often not where intuition points.

This "measure first" answer is itself the point of the question. Candidates who jump straight to useMemo without profiling reveal they optimise by habit, not evidence.

Interview note: Follow-up: "what does the Profiler's flame graph tell you?" Which components rendered in a commit and their relative render cost, so you can spot a small component re-rendering constantly or a single expensive render.

Q3. When does React.memo help, and when is it wasted?

React.memo skips a component's re-render when its props are shallow-equal to the previous ones. It helps when the component is expensive and its parent re-renders often with unchanged props. It is wasted — or harmful — when props change every render (the comparison runs and still re-renders) or when the component is cheap (the comparison costs more than the render it saves).

const Row = React.memo(function Row({ item, onSelect }) {
  return <li onClick={() => onSelect(item.id)}>{item.name}</li>;
});
// Only skips re-render if BOTH item and onSelect are stable references

Interview note: Trap: "I wrapped it in memo but it still re-renders — why?" A prop is a new reference each render (an inline object, array or function). Stabilise those with useMemo/useCallback, or memo has nothing to skip.

Q4. useMemo vs useCallback — and the honest case against overusing them.

useMemo caches a value; useCallback caches a function reference. Use them for a genuinely expensive computation, or to keep a stable reference that a React.memo child or an effect dependency relies on. The honest caveat: they are not free — each holds memory and adds a dependency array to maintain — so the default should be not using them.

const filtered = useMemo(
  () => rows.filter(r => r.active), // skip re-filtering unless rows change
  [rows]
);

Interview note: Follow-up: "should you memoize every function you pass down?" No — only functions passed to memoized children or used as effect dependencies. Blanket useCallback adds noise and memory for no benefit.

Q5. How does colocating state improve performance?

Moving state down to the component that actually uses it (colocation) means updates stay local and do not re-render unrelated siblings. Conversely, state that lives too high re-renders a whole subtree on every change. Colocation is often the highest-leverage fix and requires no memoization at all — it changes where renders happen, not just how many.

The classic example: a form where one input's value sits in a top-level component re-renders the entire form on every keystroke. Push that state into the field and the cascade disappears.

Interview note: Trap: "the whole page re-renders when I type in one field — memoize?" First move the field's state into the field; colocation beats memoizing the entire page around a misplaced piece of state.

Q6. How does code splitting improve load performance?

Code splitting breaks the bundle into chunks loaded on demand, usually with React.lazy and Suspense, most naturally at route boundaries. It shrinks the initial JavaScript the user downloads and parses before the app is interactive — frequently the single biggest win for first-load and INP.

const Dashboard = React.lazy(() => import("./Dashboard"));
// <Suspense fallback={<Spinner />}><Dashboard /></Suspense>

Interview note: Follow-up: "where do you split?" At routes first (users rarely need every page's code upfront), then heavy optional components like editors, charts or modals that not every session opens.

Q7. How do you render very long lists efficiently?

Virtualize (window) the list: render only the rows visible in the viewport plus a small overscan buffer, recycling DOM nodes as the user scrolls, using a library like react-window or react-virtual. This keeps the DOM node count and render cost constant regardless of whether the list has 100 or 100,000 items.

Rendering 10,000 real DOM rows is slow to mount, slow to update and heavy in memory; virtualization sidesteps all three by only ever rendering what is on screen.

Interview note: Trap: "you virtualized but Ctrl+F and accessibility broke — why?" Off-screen rows are not in the DOM, so browser find and some screen-reader flows miss them; you weigh that trade-off and add proper ARIA and, if needed, alternative search.

Q8. What do useTransition and useDeferredValue do for responsiveness?

They separate urgent updates from non-urgent ones so the UI stays responsive under heavy work. useTransition marks a state update as interruptible, so a keystroke stays instant while an expensive list re-renders in the background; useDeferredValue lets a derived value lag behind its source so the input updates immediately. Neither makes the heavy work faster — they make it non-blocking.

const [isPending, startTransition] = useTransition();
function onChange(e) {
  setText(e.target.value);                          // urgent: input responsive
  startTransition(() => setResults(search(e.target.value))); // non-urgent
}

Interview note: Trap: "does a transition speed up the search?" No — the search costs the same; the transition just lets React interrupt it for urgent updates so typing never stalls.

Q9. How do you keep expensive renders and effects from re-running?

Give effects honest, minimal dependencies so they run only when needed; memoize expensive derived values; and split components so a fast-changing piece does not sit inside a slow-rendering one. The pattern is separation: isolate the volatile part so its frequent updates do not drag the expensive part along. This scales into the architecture discussion in the 5-year experience set.

Interview note: Follow-up: "an effect re-runs every render — cause?" A dependency is an unstable reference (object/function literal) recreated each render; memoize it or restructure so the dependency is stable.

How to prepare

Take a deliberately slow component — a big list, or a form that re-renders the world on each keystroke — and fix it end to end: profile it, identify the real cause, apply the one right fix (colocation, virtualization, or code splitting), and re-profile to prove the win. Doing that once teaches the "measure first" discipline better than any amount of reading, and it is exactly the story interviewers want. Rehearse naming each tool's cost, since overusing memoization is the trap they probe. Pair this with the state management set (colocation and re-renders overlap heavily) and the 5-year architecture questions, refresh concurrent features in the React learning path, and use a mock interview to practise defending your fix under follow-ups.

Frequently Asked Questions

What is the first step in optimizing a slow React app?
Measure before changing anything. Use the React Profiler to find which components render often or expensively, and browser tools plus web vitals for load and interaction latency. Optimizing without profiling usually adds complexity to code that was never the bottleneck.
When does React.memo actually help?
When a component is expensive to render and its parent re-renders often while passing the same props. React.memo skips the re-render on shallow-equal props. It does nothing if the props change every render, so it must be paired with stable props via useMemo or useCallback.
What is the difference between useMemo and useCallback?
useMemo caches a computed value; useCallback caches a function reference. useCallback(fn, deps) equals useMemo(() => fn, deps). Both exist to avoid recomputation or to keep a stable reference for memoized children and effect dependencies.
How do you render a list of ten thousand rows efficiently?
Virtualize it: render only the rows currently visible in the viewport plus a small buffer, using a windowing library. This keeps the DOM small and the render fast regardless of total row count.
What do concurrent features add for performance?
useTransition marks a state update as non-urgent so typing stays responsive while an expensive update renders in the background, and useDeferredValue lets a value lag so the input updates immediately. They keep the UI responsive; they do not make the work itself faster.

Want to Build Your Career in Java Full Stack with AI?

Join CodeBegun and train with working industry engineers — Check the Java Full Stack training details

Apply for Demo Class →
Siva Prasad Galaba
Founder, CodeBegun · Staff Engineer

Founder of CodeBegun. 15+ years building Java systems at companies like Crunchyroll. Teaches Java, Spring Boot and system design the way the industry actually works, and mentors students through projects, mock interviews and placement preparation.

Technically reviewed by CodeBegun Technical TeamLast reviewed 16 July 2026 LinkedIn
Chat with us