React Interview Questions – Core Concepts to Advanced Patterns

Reviewed by Mark Dickie · Last updated

React is a JavaScript library for building user interfaces through a component tree, where each component describes its output as a function of state and props. Interviews test a predictable set of ideas: how the virtual DOM diffing works, the rules and mechanics of hooks, controlled vs. uncontrolled forms, reconciliation, and performance tools like memo and useCallback. You should also be comfortable explaining the trade-offs between lifting state up and reaching for a context or external store. The questions across this quiz span difficulty 1–5, so expect both "explain useState" warm-ups and "why does this useEffect create a stale closure?" deep-dives.

What interviewers actually care about

React interviewers at most companies care less about API trivia and more about your mental model of re-renders. If you can trace a state update from the event handler through the reconciler to the DOM, you can answer most follow-up questions on the fly.

The areas that come up most often, ranked by frequency in practice:

  1. HooksuseState, useEffect, useRef, useMemo, useCallback, and the rules that govern all of them (only call at the top level, only call from React functions).
  2. Component rendering — what triggers a re-render, how React bails out of re-rendering with memo, and when that optimisation actually helps vs. costs you more than it saves.
  3. State architecture — local state vs. lifted state vs. context vs. an external store like Zustand or Redux; knowing when each one is the right fit.
  4. The virtual DOM and reconciliation — how React diffs two trees, why key props matter on lists, and what happens when you get them wrong.
  5. Concurrent ReactuseTransition, useDeferredValue, and the Suspense boundary model introduced in React 18.

Quick-reference: hooks you must know cold

| Hook | Primary purpose | Common interview trap | |---|---|---| | useState | Holds local component state | Stale state in async callbacks; batched updates in React 18 | | useEffect | Synchronise a component with an external system | Missing or over-specified dependency array | | useRef | Persist a value across renders without triggering a re-render | Mutating .current during render | | useMemo | Cache an expensive computed value | Overusing it; the memo itself has a cost | | useCallback | Cache a function reference | Only worthwhile when the function is a prop to a memo-wrapped child | | useContext | Read a context value | Every consumer re-renders when the context value changes reference | | useReducer | Manage state with explicit action dispatch | Good fit when next state depends on previous state in complex ways | | useTransition | Mark a state update as non-urgent | Does not work for updates driven by external data — use useDeferredValue for that |

Before you sit down for the interview

A short checklist worth running through the day before:

  1. Build a small component from scratch using only hooks — no class components. Muscle memory matters when you are screen-sharing.
  2. Read the React 18 release notes on automatic batching and the new root API (createRoot). Interviewers at companies on React 18 ask about it directly.
  3. Practise explaining the dependency array out loud. Most candidates know the rule; far fewer can explain why the rule exists in terms of closures.
  4. Revisit error boundaries — they are still class-component-only as of React 18, and that surprises people who have written only functional components.
  5. Know one real performance debugging story. "I wrapped everything in useMemo" is a red flag to a senior interviewer; "I used the React DevTools Profiler to find the slow re-render, then fixed it by stabilising a callback reference" is not.

At a glance

Questions15
Difficulty2–4 of 5
FormatsMultiple choice, Multiple answer, True / false, Find the bug, Short answer, Flashcard

What you'll review

  1. useeffect deps
  2. usestate
  3. rerender causes
  4. rules of hooks
  5. keys
  6. useeffect cleanup
  7. usememo usecallback
  8. lifting state
  9. react memo
  10. strict mode
  11. data fetching
  12. rsc basics
  13. client boundaries
  14. code splitting
  15. controlled inputs

Practice questions

React/hooks/useeffect-deps

When does the effect in useEffect(fn, []) run?

Options

  • After the first render (on mount), and never again
  • After every render
  • Synchronously before the first render
  • Only when the component unmounts
Show answer

It runs once, after the component first mounts, and never again. An empty dependency array tells React the effect depends on nothing that changes between renders, so there is no trigger to re-run it. Its optional cleanup function runs once, when the component unmounts. Reach for [] when the work should happen a single time — subscribing, fetching initial data, or setting up a one-off listener.

Why:

An empty dependency array means the effect has no dependencies that change, so it runs once after mount. Its optional cleanup runs on unmount.

React/hooks/usestate

What is the correct way to increment state that depends on its previous value?

Options

  • setCount(c => c + 1)
  • setCount(count + 1) called twice in a row
  • count++ then setCount(count)
  • count = count + 1
Show answer

Use the updater form: setCount(c => c + 1). React passes the latest pending state into the function, so it stays correct across batching and avoids stale-closure bugs. Reading the count variable directly — setCount(count + 1) — captures the value from the current render, so calling it twice still increments only once. Whenever the next state depends on the previous, pass a function, not a value.

Why:

The updater form setCount(c => c + 1) receives the latest pending state, so it is safe across batching and stale closures. Reading the count variable can use a stale value.

React/performance/rerender-causes

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

Options

  • Calling its state setter with a new value
  • Receiving new props
  • Mutating a plain (non-state) variable
  • Its parent re-rendering
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.

React/hooks/rules-of-hooks

Which are actual Rules of Hooks?

Options

  • Only call hooks at the top level of a component or hook
  • Only call hooks from React function components or custom hooks
  • Hooks may be called conditionally inside an if
  • Hooks may be called inside a for loop
Show answer

Two rules: only call hooks at the top level of a component or custom hook, and only call them from React function components or custom hooks — never from ordinary JavaScript functions. Both follow from the same mechanism: React identifies each hook by its call order, which must stay identical on every render. That's why a hook can't sit inside an if or a for loop, where the order could change between renders.

Why:

Hooks must run in the same order on every render, so they cannot be placed inside conditions or loops, and they may only be called from React functions or custom hooks.

React/rendering/keys

When a list can be reordered or filtered, using the array index as the key can cause incorrect UI state.

Show answer

True. Using the array index as the key ties each element's identity to its position, so when a list is reordered or filtered, React reuses the wrong DOM and component state for the wrong item — leaving stale input values or selections attached to the wrong row. A stable, unique id per item avoids this by giving each element an identity that survives reordering.

Why:

Index keys tie an element's identity to its position, so when items move, React reuses the wrong DOM/state for the wrong item. Use a stable unique id instead.

React/hooks/useeffect-cleanup

This component leaks timers and never advances past 1. Which line is the source of the bug?

function Timer() {
  const [n, setN] = useState(0);
  useEffect(() => {
    setInterval(() => setN(n + 1), 1000);
  }, []);
  return <span>{n}</span>;
}
Show answer

The bug is on line 4.

Why:

The interval is never cleared (the effect returns no cleanup), so it leaks, and it captures a stale n of 0 so it forever sets state to 1. Fix: keep the id and return () => clearInterval(id), and use setN(p => p + 1).

React/hooks/usememo-usecallback

What do useMemo and useCallback do, and when should you use them?

Show answer

Both memoize across renders: useMemo caches a computed value and useCallback caches a function reference, recomputing only when their dependencies change. Use them to skip genuinely expensive recomputation or to keep a stable reference for a memoized child or an effect dependency — not as a blanket optimization, since they add overhead.

Why:

useCallback(fn, deps) is just useMemo(() => fn, deps). They matter mainly when a stable identity prevents wasted renders downstream or expensive work each render.

React/state/lifting-state

What is the difference between state and props?

Show answer

Props are inputs passed in by a parent and are read-only to the receiving component. State is data the component owns and can change over time (via its setter), triggering a re-render.

Why:

Data flows down as props; a component manages its own state. Lifting state up means moving shared state to a common ancestor and passing it back down as props.

React/performance/react-memo

What does wrapping a component in React.memo do?

Options

  • It skips re-rendering the component when its props are shallowly equal to the previous render's props
  • It memoizes the component's internal state so it survives unmounting
  • It caches the component's return value forever, ignoring all prop changes
  • It prevents the component's own useState updates from re-rendering it
Show answer

It skips re-rendering the component when its props are shallowly equal to the previous render's props. React.memo 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.

React/rendering/strict-mode

In development, React 18 Strict Mode intentionally does what to help surface bugs?

Options

  • Double-invokes renders, effects (setup+cleanup+setup), and certain other functions to expose impure logic and missing cleanup
  • Runs all effects twice in production builds as well
  • Disables all effects until the component is interacted with
  • Throws an error whenever a component re-renders more than once
Show answer

It double-invokes renders, effects (setup→cleanup→setup), and certain other functions to expose impure logic and missing cleanup. By running these an extra time in development, Strict Mode surfaces non-idempotent renders and effects that forget to clean up. This happens in development only — production builds run everything once, so the extra invocations never affect real users.

Why:

Strict Mode re-runs render functions and runs each effect setup→cleanup→setup an extra time in development only, so non-idempotent renders and effects that forget cleanup show up immediately. This does not happen in production.

React/effects/data-fetching

When fetching data inside useEffect, why should the effect handle cleanup or ignore stale responses?

Options

  • Because dependencies can change before a request resolves, and a slow earlier response can overwrite a newer one (a race condition)
  • Because fetch cannot be called inside an effect at all
  • Because effects always run synchronously and block the network request
  • Because React caches every fetch automatically, so cleanup clears that cache
Show answer

Because dependencies can change before a request resolves, and a slow earlier response can overwrite a newer one — a race condition. If an id changes while a fetch is in flight, two requests overlap, and the slower earlier one may land last and clobber the fresh data. Cleanup that ignores the stale response, via a flag or an AbortController, prevents that. Effects can call fetch perfectly well.

Why:

If the dependency (e.g. an id) changes while a request is in flight, two fetches overlap; without ignoring the stale one (via a cleanup flag or AbortController) an earlier, slower response can clobber the newer data. Effects can absolutely call fetch.

React/server-components/rsc-basics

Which statement about React Server Components (RSC) is correct?

Options

  • They render on the server, can be async and access server resources directly, and cannot use state or effects like useState/useEffect
  • They run in the browser and can use useState and useEffect freely
  • They ship their component code to the client like ordinary components
  • They replace the need for any client components in an app
Show answer

React Server Components render on the server, can be async, and access server resources like files and databases directly — but they cannot use state or effects such as useState/useEffect. Their code is never shipped to the browser, which keeps bundles small but means they have no client interactivity. Anything that needs state, effects, or event handlers belongs in a client component instead.

Why:

Server Components execute only on the server, so they can be async and read server-only resources (files, DB) directly, but they have no client interactivity primitives like useState/useEffect and their code is not sent to the browser. Interactive pieces are client components.

React/server-components/client-boundaries

What does the "use client" directive at the top of a file do?

Options

  • It marks the module (and the component subtree imported from it) as a client boundary, so those components run in the browser and may use state, effects, and event handlers
  • It forces the component to render only on the server
  • It disables hydration for the whole application
  • It is required at the top of every React component file
Show answer

It marks the module, and the component subtree imported from it, as a client boundary — so those components are bundled for and run in the browser, where they may use state, effects, and event handlers. Server components are the default in an RSC app, so you only add "use client" at the points where interactivity is actually required; it is not needed on every file.

Why:

"use client" opts a module into the client: it and the components it exports become a client boundary that is bundled for and runs in the browser, enabling useState, useEffect, and event handlers. Server components are the default in an RSC app, so the directive is only needed where interactivity is required.

React/performance/code-splitting

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

Options

  • React.lazy defers loading a component's code until it renders, and <Suspense fallback={…}> shows the fallback while that chunk loads
  • React.lazy runs the component on a background thread while <Suspense> waits for it
  • <Suspense> caches the lazy component so it only ever renders once
  • React.lazy requires no <Suspense> boundary and renders nothing while loading
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.

React/forms/controlled-inputs

Which are true of a controlled <input value={text} onChange={e => setText(e.target.value)} />?

Options

  • React state is the single source of truth for the input's value
  • Each keystroke triggers a state update and a re-render
  • You can read the current value at any time from state, without a ref
  • Providing value without an onChange makes it freely editable
Show answer

Three statements are true: React state is the single source of truth for the value, each keystroke triggers a state update and a re-render, and the current value is always readable from state without a ref. What is false is that providing value without an onChange makes it freely editable — that combination makes the input read-only, since nothing ever updates the state driving it.

Why:

A controlled input derives its displayed value from state, so state is authoritative, every keystroke flows through onChangesetState→re-render, and the value is always available in state. Setting value with no onChange makes it read-only, not freely editable.

Sources

The official documentation these questions are checked against:

Related interview questions

Job market

See react salaries and hiring demand from live job postings.

Practice this for real

CodePrep turns your target job description into an adaptive quiz from a bank of tagged questions, scores your answers, and resurfaces the topics you miss.

New topics and job-market signal, in your inbox

Occasional updates — new question topics, launch news, and what the developer job market is hiring for. Confirm your email to join, and unsubscribe anytime.