React Interview Questions and Answers
Short answer
React interviews rest on one model: what causes a component to re-render, and what React does with the result. Reconciliation, immutable state, keys, memo and where state should live are all consequences of that model rather than separate topics to memorise.
| Stage | What is asked | What decides the score |
|---|---|---|
| Fundamentals | Virtual DOM, props vs state, keys | Whether you explain the why, not the what |
| Hooks | useState, useEffect, dependencies | Stale closures and cleanup |
| State design | Where should this state live? | Lifting, colocating, and not over-globalising |
| Performance | Why does this re-render? | Knowing memo does nothing without stable props |
| Machine coding | Build a component, live | State placement and cleanup, not typing speed |
| Ecosystem | Routing, data fetching, Redux or Query | Picking by problem, not by familiarity |
| Modern React | Server Components, React 18/19 | Whether you have kept up |
How the React round is actually structured
Most React interviews at product companies in India run 45 to 60 minutes and move through three phases: fifteen minutes of fundamentals, twenty of digging into something you claimed on your CV, and the rest on a small live component or a design discussion. The fundamentals section is a filter rather than the assessment, clear answers buy you the rest of the hour to talk about things where you can actually differentiate.
What has changed in the last two years is the weight given to the ecosystem. "Do you know React?" is now mostly settled by whether you can talk about where state belongs, why a component re-renders, and which tool handles server data, not by whether you can define the virtual DOM. The definitional questions still open the round, so answer them crisply, but treat them as the price of admission rather than the exam.
What is the virtual DOM and how does reconciliation work?
The virtual DOM is a plain JavaScript description of what the UI should look like. On each render React builds a new tree, compares it with the previous one, and applies only the differences to the real DOM. Comparing two arbitrary trees is O(n cubed), so React uses two heuristics to get it to O(n): different element types produce a completely new subtree, and elements are matched by key within a list.
1// Same type: React keeps the DOM node and updates only what changed
2<div className="a" title="x" />
3<div className="b" title="x" />
4// → one attribute mutation; the node and any child state survive
5
6// Different type: the entire subtree is destroyed and rebuilt
7{isEditing ? <div><Input /></div> : <span><Input /></span>}
8// → Input unmounts and remounts, losing all of its state
9// → the fix is to keep the element type stable and change the props
10
11// Conditional rendering that accidentally changes structure
12{loading ? <Spinner /> : <><Header /><List /></>}
13// → fine: siblings are matched by position, and both branches differ anyway
14
15// The point people miss: the virtual DOM is not "fast".
16// Touching the real DOM is expensive, so batching the changes and
17// applying the minimum set is what makes it fast. A hand-written
18// direct DOM update will always beat it, React trades a little
19// speed for not having to write that by hand and get it right.That last point is the mature version of this answer and is worth making. The virtual DOM is not faster than the DOM; it is a way to get a declarative programming model at an acceptable cost. Claiming it is faster than direct manipulation is a common answer, and interviewers who know the internals will follow up on it.
What actually triggers a re-render?
Three things, and only three: the component's own state changes, a context it consumes changes, or its parent re-renders. Notice what is not on that list, a prop changing does not itself cause a re-render, because the only way a prop can change is that the parent re-rendered, which was already going to re-render the child.
1function Parent() {
2 const [count, setCount] = useState(0);
3 return (
4 <>
5 <button onClick={() => setCount((c) => c + 1)}>{count}</button>
6 <ExpensiveChild /> {/* no props at all */}
7 </>
8 );
9}
10// → ExpensiveChild re-renders on every click. Props did not change;
11// → the parent re-rendered, and that is enough.
12
13// Fix 1: memo, skip if props are shallowly equal
14const ExpensiveChild = React.memo(function ExpensiveChild() { return null; });
15// → now it renders once. With no props, shallow equality always holds.
16
17// But memo does nothing if a prop is recreated each render:
18<ExpensiveChild style={{ margin: 0 }} onSelect={() => {}} />
19// → a new object and a new function every time, so memo always misses
20// → useMemo / useCallback exist to make those references stable
21
22// Fix 2 (often better): children as a prop
23function Parent({ children }) {
24 const [count, setCount] = useState(0);
25 return <><button onClick={() => setCount((c) => c + 1)}/>{children}</>;
26}
27// <Parent><ExpensiveChild /></Parent>
28// → children was created by the GRANDPARENT, so the element is the same
29// → object across Parent's re-renders and React skips re-rendering it,
30// → with no memo and no dependency arrays anywhereThe `children` pattern is the answer that lands hardest, because it solves the problem structurally rather than by adding memoisation. It also explains why moving state down, putting the counter in its own small component, is usually the first thing to try: a re-render that only touches a button is not a problem worth optimising in the first place.
Props, state, and why state must be immutable
Props are read-only inputs from the parent; state is data the component owns and can change. The rule that matters more than the definition is that state must never be mutated, because React decides whether anything changed by comparing references with `Object.is`, mutate an object in place and the reference is identical, so the update is invisible.
1const [user, setUser] = useState({ name: "A", prefs: { theme: "dark" } });
2
3// Nothing happens: same object, so Object.is says nothing changed
4user.name = "B";
5setUser(user);
6// → no re-render
7
8// Correct: a new object at every level that changed
9setUser({ ...user, name: "B" });
10setUser({ ...user, prefs: { ...user.prefs, theme: "light" } });
11// → prefs must be spread too; a shallow spread shares the nested object
12
13// Arrays: the same rule
14const [items, setItems] = useState([1, 2, 3]);
15setItems([...items, 4]); // add
16setItems(items.filter((i) => i !== 2)); // remove
17setItems(items.map((i) => (i === 3 ? 30 : i)));// update one, keep the rest
18// → mapping keeps every unchanged element's identity, so memoised
19// → children that received them do not re-render
20
21// items.push(4); setItems(items); → same reference, no re-renderThe last comment is the performance half of the answer and worth volunteering. Updating one row with `map` leaves every other element referentially identical, so a memoised list item that received one of them skips its render. Rebuilding the whole array - or deep-cloning the state, produces the same visible output while quietly re-rendering everything.
Why do keys matter in lists?
Keys tell React which element in the new list corresponds to which in the old one. With a stable id, React moves the existing DOM node and its state to the new position. With the array index, React matches by position instead, so when items are reordered, inserted at the front, or removed from the middle, state and DOM attach to the wrong row.
1// Each row holds uncontrolled input state
2{todos.map((t, i) => <Row key={i} todo={t} />)}
3// → delete the first todo, and every row shifts down one index.
4// → React thinks row 0 simply changed its props, so the text typed
5// → into the deleted row's input is now attached to the SECOND todo.
6
7{todos.map((t) => <Row key={t.id} todo={t} />)}
8// → React matches by id, unmounts exactly the deleted row, and every
9// → other row keeps its DOM node and its state
10
11// When an index key is fine: a static list that is never reordered,
12// filtered or added to, and whose items hold no state.
13
14// Changing a key deliberately is a feature, not a workaround:
15<ProfileEditor key={userId} user={user} />
16// → switching users remounts the editor, resetting its draft state
17// → this replaces an entire useEffect that reset the form on prop changeThe second half of that snippet is what makes this answer memorable. Most candidates know index keys are bad; far fewer know that changing a key on purpose is the idiomatic way to reset a component's state when an identity changes. Offering both directions turns a rote question into evidence you understand the mechanism.
This is 1 of 75+ questions in the React Interview Kit
Get every question with detailed answers, follow-ups and real code, plus coding challenges and a last-minute revision sheet. One-time payment, instant access.
⚡ Get the React Interview Kit → ₹399What is React.memo and when does it help?
`React.memo` wraps a component so it skips re-rendering when its props are shallowly equal to last time. It helps only when two things are true at once: the component is genuinely expensive to render, and its props are referentially stable. Miss the second and it never skips anything, so you have added a comparison on every render and gained nothing.
This is where `useMemo` and `useCallback` earn their place, and it is worth being explicit that their purpose is referential stability rather than speed. `useCallback` keeps a function identity constant so a memoised child's props compare equal; `useMemo` does the same for an object or an expensive computation. Applying them everywhere is a real cost, extra allocations, extra comparisons, and dependency arrays that go stale, which is why the honest answer to "should I memoise this?" is usually to profile first. React's own compiler is designed to remove most of this manual work over time, and saying so shows you know where the ecosystem is heading.
Where should state live?
As close to where it is used as possible, and lifted only as far as the nearest common ancestor of the components that genuinely need it. This is the design question underneath most React interviews, and getting it right removes the need for most of the performance work that gets asked about later.
1// Too high: the whole page re-renders on every keystroke
2function Page() {
3 const [query, setQuery] = useState("");
4 return <><Header /><Sidebar /><Search value={query} onChange={setQuery} /></>;
5}
6
7// Colocated: only Search re-renders
8function Page() {
9 return <><Header /><Sidebar /><Search /></>;
10}
11
12// Derived state is a bug waiting to happen
13const [items, setItems] = useState([]);
14const [count, setCount] = useState(0); // → can drift out of sync
15const count = items.length; // → cannot
16
17// The same rule for filtering
18const [filtered, setFiltered] = useState([]); // → needs an effect to maintain
19const filtered = items.filter((i) => i.done); // → always correct, one render
20
21// Ask in order: can it be derived? can it live lower? does it belong
22// in the URL (filters, tabs, pagination) or in a query cache (server data)?
23// Only what survives all four questions needs component state at all.That four-question sequence is a strong thing to say out loud, because it is how experienced developers actually decide and it pre-empts the follow-up about global state. Server data belongs in a query cache; filters and pagination belong in the URL so a refresh and a shared link both work; anything derivable should be computed during render. What remains is usually a small amount of genuinely local state.
What is the Context API for, and what does it cost?
Context is a transport mechanism: it lets a value reach a deep descendant without threading it through every intermediate component. It is not a state manager, and the distinction matters because of its cost, when the context value changes, every consumer re-renders, and `memo` cannot prevent it since the value did not arrive as a prop.
1// A new object every render, so every consumer re-renders every time
2<AuthContext.Provider value={{ user, login, logout }}>
3
4// Stable identity: only changes when user actually changes
5const value = useMemo(() => ({ user, login, logout }), [user]);
6<AuthContext.Provider value={value}>
7
8// Better still: split rarely-changing values from frequently-changing ones
9<AuthContext.Provider value={user}> {/* changes on login */}
10 <ThemeContext.Provider value={theme}> {/* changes almost never */}
11// → a theme toggle no longer re-renders every component that reads auth
12
13// Rule of thumb: Context suits values that change rarely, theme, locale,
14// the current user. For state that changes often and is read widely,
15// a store with fine-grained subscriptions (Zustand, Redux) avoids
16// re-rendering every consumer on every change.Why does StrictMode render everything twice?
In development only, StrictMode deliberately double-invokes render functions, state updaters and effects to surface bugs that would otherwise appear intermittently in production. A render function that mutates a variable outside itself, or an effect with no cleanup, behaves visibly wrong under double invocation and correctly without it, so React makes the bug loud during development.
The two consequences worth naming: render must be pure, so no mutation of external variables, no API calls and no `Math.random()` written into state during render; and every effect must be safe to run twice, which in practice means it must have a cleanup that fully undoes it. If a component breaks under StrictMode, that is a real bug the mode found, not a React quirk to work around, and saying it does not run in production closes the question cleanly.
What changed in React 18 and 19?
React 18 introduced concurrent rendering and, with it, automatic batching everywhere, including inside promises and timeouts, not only React event handlers, plus `useTransition` and `useDeferredValue` for keeping input responsive during expensive updates, and Suspense for streaming server rendering.
React 19 added Server Components as a stable model, Server Actions for mutations, the `use` hook that can read a promise or context and is the one hook allowed inside a condition, form actions with `useFormStatus` and `useOptimistic`, ref as a regular prop rather than requiring `forwardRef`, and native support for document metadata. You will not be asked to implement any of these, but naming three or four accurately is the difference between a candidate who follows React and one who learnt it once.
What loses marks in a React round
The failures are consistent, and most of them are about the model rather than the API.
- →Saying the virtual DOM is faster than direct DOM manipulation.
- →Not knowing a parent re-render re-renders children regardless of props.
- →Reaching for memo before checking whether the re-render costs anything.
- →Mutating state and then being unable to explain why nothing happened.
- →Using index keys on a list that can be reordered or filtered.
- →Storing derived values in state and syncing them with an effect.
- →Putting all state at the top of the tree because it might be needed later.
- →Describing class components and lifecycle methods as your default model.
Frequently asked questions
- What is the virtual DOM and how does reconciliation work?
- The virtual DOM is a JavaScript description of the UI. On each render React builds a new tree, diffs it against the previous one and applies only the differences to the real DOM. Two heuristics keep it linear: a different element type replaces the whole subtree, and list items are matched by key.
- Is the virtual DOM faster than direct DOM manipulation?
- No. Hand-written, targeted DOM updates are faster. The virtual DOM makes a declarative model affordable by batching changes and applying the minimum set, so you get maintainable code at an acceptable cost. Claiming it is inherently faster is a common answer that invites a follow-up you cannot win.
- What causes a React component to re-render?
- Its own state changing, a context it consumes changing, or its parent re-rendering. A prop changing is not a separate cause, because props only change when the parent re-rendered. A parent re-render propagates to every child by default, whether or not their props changed.
- What is the difference between props and state?
- Props are read-only inputs passed down from a parent and a component must never modify them. State is data the component owns, changed through its setter, and updating it schedules a re-render. Anything that can be computed from props should be derived during render, not copied into state.
- Why must React state be treated as immutable?
- React compares the previous and next values with Object.is to decide whether anything changed. Mutating an object or array in place leaves the reference identical, so the update is invisible and no re-render happens. Always create a new value with spread, map or filter.
- Why are keys important in React lists?
- They tell React which new element corresponds to which old one. A stable id lets React move an item's DOM node and state to its new position; the array index matches by position instead, so reordering or deleting attaches state and DOM to the wrong row.
- When is it acceptable to use the array index as a key?
- When the list is static, never reordered, filtered, inserted into or deleted from, and its items hold no state of their own. In practice that is rarer than people assume, and the cost of a stable id is usually nothing, so it is the safer default.
- When does React.memo actually help?
- Only when the component is genuinely expensive to render and its props are referentially stable. If a parent passes a fresh object or inline function each render, shallow comparison always fails and memo just adds work. That is why memo usually has to be paired with useMemo or useCallback to do anything.
- Where should state live in a React app?
- As low in the tree as possible, lifted only to the nearest common ancestor that genuinely needs it. Before adding state, ask whether the value can be derived during render, whether it belongs in the URL as with filters and pagination, or whether it is server data that belongs in a query cache.
- Is the Context API a replacement for Redux?
- No. Context is a transport mechanism for passing a value deep into the tree; it has no store, selectors, middleware or fine-grained subscriptions. Every consumer re-renders when the value changes, which makes it a good fit for rarely-changing values like theme or the current user, and a poor one for high-churn state.
- Why does React render components twice in development?
- StrictMode double-invokes renders, state updaters and effects in development to surface impurities, a render that mutates external state, or an effect with no cleanup, that would otherwise cause intermittent production bugs. It does not happen in production builds, and a component that breaks under it has a real bug.
- What are the most important React topics for interviews?
- What triggers a re-render, immutable state updates, keys and reconciliation, hooks with their dependency arrays and cleanup, where state should live, and enough of the ecosystem to say which tool handles server data. Class components and lifecycle methods matter only for maintaining existing code.
This is 1 of 75+ questions in the React Interview Kit
Get every question with detailed answers, follow-ups and real code, plus coding challenges and a last-minute revision sheet. One-time payment, instant access.
⚡ Get the React Interview Kit → ₹399