The interviewer opens your portfolio project, scrolls to a component and asks, "Why does this effect run twice when the page loads?" You have used useEffect a hundred times, but never had to explain it, and "I think it's a bug in React" is the first sentence that comes out.
React interviews are rarely about whether you can build a component. They are about whether you know what React does with it after you hand it over.
The short answer
React interview questions cluster around three things: how React renders (the virtual DOM, reconciliation, keys and why re-renders happen), how state works (props versus state, batching, lifting state, forms) and how hooks behave (effect dependencies, cleanup, stale closures, memoisation and the rules of hooks). Interviewers expect answers in current React (18 and 19), with hooks, not class lifecycles. Each of the 25 React interview questions and answers below explains why, and names the follow-up you are most likely to hear next. If your JavaScript is shaky, start with the JavaScript interview questions: half of React's surprises are closures in disguise.
Rendering
1. What is the virtual DOM, and how does reconciliation work?
The virtual DOM is the tree of plain JavaScript objects your components return. When state changes, React calls your components again to get a new tree, compares it with the previous one, and applies only the differences to the real DOM. That comparison is reconciliation. React uses two heuristics to keep this fast: elements of a different type (a div becoming a section, or ProfileA becoming ProfileB) are treated as entirely new subtrees and rebuilt, and children in a list are matched by their key.
Likely follow-up: "So is the virtual DOM faster than the real DOM?" Not inherently. Its value is that you describe the UI and React works out the minimal updates for you.
2. Why does React need keys in lists, and why is the index a bad key?
Keys tell React which item is which between renders, so it can keep the right component state and DOM node attached to the right item. Insert an item at the top of an index-keyed list and every index shifts, so React attaches old state to the wrong rows: a half-typed input appears to jump. Use a stable ID from your data.
Likely follow-up: "When is the index acceptable?" When the list is static, never reordered or filtered, and its items hold no state.
3. What causes a component to re-render?
Three things: its own state changes, its parent re-renders, or a context it reads changes. Note what is missing: props changing is not a separate trigger. A child re-renders because its parent rendered, whether or not the props are different.
Likely follow-up: "Is a re-render a problem?" Usually not. When it is, measure with the React DevTools Profiler before optimising.
4. What does React.memo do?
It wraps a component so that, when its parent re-renders, React skips rendering it if every prop is shallowly equal to last time. The catch is shallow equality: an object or function created inline in the parent is a new reference every render, so the check always fails.
Likely follow-up: "Does React.memo stop re-renders from context?" No. A memoised component that reads a context still re-renders when that context value changes.
State and props
5. What is the difference between props and state?
Props are inputs passed in by the parent; the component must treat them as read-only. State is data the component owns and changes over time with its setter. Decide by ownership: whoever changes a value holds it as state and passes it down.
6. If I call setCount(count + 1) three times in one handler, what happens?
The count goes up by one, not three. All three calls read the same count from that render, so each one sets the same value. React also batches the updates and re-renders once. To get three, use the updater form: setCount(c => c + 1), which receives the latest queued value each time.
Likely follow-up: "Did batching change in React 18?" Yes. Before React 18, updates were batched only inside React event handlers. With createRoot in React 18, batching is automatic everywhere, including setTimeout, promises and native listeners.
7. Why does logging state right after setting it show the old value?
Because the setter does not mutate the variable. It schedules a re-render, and the new value arrives in the next call of your component. The count in your function is fixed for this render. If you need the new value, compute it into a local variable.
8. What does "lifting state up" mean?
When two sibling components need the same data, you move that state to their closest common parent and pass it down as props, with a callback for changes. One source of truth beats two copies drifting apart.
9. What is the difference between controlled and uncontrolled components?
A controlled input takes its value from React state and reports changes through onChange, so React is the source of truth and you can validate or transform on every keystroke. An uncontrolled input keeps its own value in the DOM; you read it when needed through a ref or the form's data on submit.
Likely follow-up: "What is the warning about switching from uncontrolled to controlled?" It appears when value starts as undefined and later becomes a string. Initialise it to ''.
10. How do you handle forms in modern React?
Small forms: controlled inputs. Larger ones: a library such as React Hook Form, which uses uncontrolled inputs to avoid a re-render per keystroke. React 19 adds form Actions: you can pass a function to <form action={...}>, and hooks such as useActionState and useFormStatus give you the pending state and the result without hand-written loading flags.
Effects
11. How does the useEffect dependency array work?
React runs the effect after the component renders and commits, then re-runs it only when a value in the dependency array has changed since the last render, compared with Object.is. No array means it runs after every render. An empty array means it runs once after mount (twice in development under Strict Mode; see question 14). Every value from the component that the effect reads belongs in the array; leaving one out does not make the effect "run less", it makes it read stale values.
Likely follow-up: "Why does my effect run in a loop?" Usually because it sets state that is also a dependency, or because a dependency is an object or function recreated every render.
12. What is the cleanup function for, and when does it run?
The function you return from an effect undoes what the effect set up: removing an event listener, clearing an interval, cancelling a subscription or ignoring a stale fetch. React runs it before the effect runs again with new dependencies, and when the component unmounts. Without it, listeners and timers leak, or an old slow response overwrites newer data.
13. What is a stale closure in React?
Every render creates new functions that close over that render's props and state. If a function outlives its render, say an interval set up in an effect with an empty dependency array, it keeps reading the values from the first render forever. The classic symptom is a counter stuck at 1. Fixes: add the value to the dependencies, use the updater form of the setter, or keep the latest value in a ref.
14. Why do my effects run twice in development?
That is <StrictMode>, not a bug. Since React 18, Strict Mode in development mounts each component, unmounts it, and mounts it again, so every effect runs setup, cleanup, setup. This surfaces effects missing a proper cleanup. Strict Mode also double-calls component bodies and state updaters to catch impure code. None of this happens in a production build.
Likely follow-up: "So should I use a ref to stop it running twice?" No. Write the cleanup so that running twice is harmless.
15. When should you not use useEffect?
When you are computing something from props or state, calculate it during render instead of copying it into state with an effect; that saves a render and a source of bugs. Responses to user actions belong in event handlers. Effects are for synchronising with something outside React: the network, timers, the DOM, a third-party widget.
Hooks
16. What are the rules of hooks, and why do they exist?
Call hooks only at the top level of a component or a custom hook, never inside conditions, loops or nested functions, and never from plain JavaScript functions. React does not know hooks by name; it identifies them by the order they are called in each render. If a condition skips one, every hook after it gets the wrong stored state.
Likely follow-up: "Are there exceptions?" One. React 19's use API can be called inside conditions and loops, though still only from a component or a hook.
17. What do useMemo and useCallback do, and when should you not use them?
useMemo caches a computed value and useCallback caches a function, both until their dependencies change. They exist for two cases: an expensive calculation, or keeping a reference stable so a React.memo child or an effect dependency does not change every render. Do not wrap everything. Each costs memory and a comparison, and memoising for a non-memoised child achieves nothing.
Likely follow-up: "Does the React Compiler change this?" Yes. The React Compiler memoises components and values automatically at build time, so in projects that use it, hand-written useMemo and useCallback become much less necessary.
18. What is useRef used for?
Two things. Holding a reference to a DOM node, for focusing an input or measuring an element. And holding any mutable value that should survive re-renders without causing one, such as a timer ID or the previous value of a prop. Changing ref.current never re-renders, which is the difference from state. In React 19 you can pass ref as a normal prop to a function component, so forwardRef is no longer needed for new code.
19. What is a custom hook, and why write one?
A function whose name starts with use and which calls other hooks. It lets you reuse stateful logic, such as fetching or a window size listener, across components. Each component that calls it gets its own independent state; custom hooks share logic, not data.
Context and state management
20. What does context cost in terms of re-renders?
When a provider's value changes, every component that reads that context re-renders. A common mistake is passing an object literal, value={{ user, setUser }}, which is a new object on every render of the provider, so every consumer re-renders every time. Memoise the value, and split unrelated data into separate contexts.
Likely follow-up: "Is context a state manager?" No. It is a way to pass a value down without prop drilling. The state still lives in useState or useReducer somewhere.
21. How do you choose a state management approach?
Start with local state, lift it when siblings share it, and use context for widely read values that rarely change, like the theme. Treat server data separately: TanStack Query handles caching, refetching and loading states. Reach for Zustand or Redux Toolkit when many unrelated components update shared client state often. Filters and pagination often belong in the URL.
Robustness, performance and the newer model
22. What is an error boundary?
A component that catches errors thrown during rendering in its subtree and shows a fallback UI instead of unmounting the whole app. It is the one place you still need a class component, using static getDerivedStateFromError and componentDidCatch, or a library such as react-error-boundary that wraps one. It does not catch errors in event handlers or async code; handle those with try/catch.
23. How do you do code splitting in React?
Wrap a dynamic import in React.lazy, as in const Settings = lazy(() => import('./Settings')), and render it inside <Suspense fallback={...}>. The bundler puts that component in a separate chunk that downloads only when it is first rendered. Routes are the natural split point.
24. What are Server Components, and how are they different from client components?
Server Components, stable in React 19 and used through frameworks such as the Next.js App Router, run only on the server. They can read data directly, and their code never ships in the client bundle. Because of that they cannot use state, effects or browser APIs. A file starting with 'use client' marks the boundary where client components begin, and those work the way React always has. Server-side rendering is different: it renders client components to HTML on the server and then hydrates them in the browser.
Likely follow-up: "Is 'use server' how you mark a Server Component?" No. Server Components need no directive in such a framework; 'use server' marks server functions that client code can call.
25. What changed in React 19 that is worth knowing?
The headline pieces are Actions and the hooks around them (useActionState, useFormStatus, useOptimistic), the use API for reading a promise or context, ref as a regular prop, and rendering <Context> directly as a provider instead of <Context.Provider>. You need not have used it all, but do not answer lifecycle questions in terms of componentDidMount.
How to practise these
Read each answer, close the page, and explain it out loud. Knowing an answer and being able to explain it are different skills, and that gap is what your resume says you know React: can you prove it? is about. When a follow-up goes past what you know, say what you do know and reason from there; how to handle a question you don't know covers how. For the wider plan, see the interview preparation guide for freshers.
To rehearse with someone who keeps asking "why?", you can pick React as your stack on our React interview practice page. Sia, the AI interviewer, asks one question at a time, follows up on what you actually said, and gives you a written breakdown when you finish. When you want a person instead, a live 40-minute Zoom interview with a working engineer gives you scored, written feedback on technical depth, problem solving, communication, behaviour and code quality.