React interviews are mostly about one thing: whether you understand when and why a component re-renders. Every hook question, every performance question and every stale-value bug comes back to it. These are the questions that come up, answered from that angle.
Components, props and state
The opening round, and where the follow-ups start.
Basic
Q1
What is the difference between props and state?
Props come from the parent and are read-only inside the component. State is owned by the component and changing it triggers a re-render. If two components need the same value, it belongs in state in their closest common parent and travels down as props — that is what "lifting state up" means.
Intermediate
Q2
Why does React need keys in a list?
Keys tell React which item is which between renders, so it can move, keep or remove DOM nodes instead of rebuilding them. Without stable keys it matches by position, which is why a list with an input in each row shows the wrong values after a reorder or a delete.
The follow-upWhy not use the array index as a key? It is stable only if the list never reorders or has items removed — the exact cases keys exist for. Use an id from the data.
Intermediate
Q3
What is the virtual DOM and reconciliation?
React builds a lightweight tree describing the intended UI, compares it with the previous one, and applies the minimal set of real DOM changes. That diffing is reconciliation. The point is not that the virtual DOM is fast — it is that it lets you write "what the UI should look like" instead of a list of DOM mutations.
Intermediate
Q4
What is a controlled versus an uncontrolled component?
A controlled input takes its value from state and updates state on change, so React is the single source of truth — which is what makes validation and conditional logic straightforward. An uncontrolled input keeps its own value in the DOM and you read it with a ref, which is lighter for a simple form.
Basic
Q5
Why must you never mutate state directly?
React decides whether to re-render by comparing references. Pushing onto an array in state leaves the reference unchanged, so React sees no difference and the screen does not update. Always produce a new object or array: setItems([...items, next]).
Hooks
The bulk of a modern React interview.
Intermediate
Q6
What does useEffect do, and when does it run?
It runs code after render, for things outside React's rendering — subscriptions, timers, fetching, manual DOM work. With no dependency array it runs after every render; with [] only after the first; with dependencies, whenever one of them changes by reference or value. The returned function is cleanup, and it runs before the next effect and on unmount.
useEffect(() => {
const id = setInterval(tick, 1000);
return () => clearInterval(id); // cleanup: without this, intervals stack up
}, []);
The follow-upThen: what happens if you omit the cleanup? Every re-mount adds another interval or subscription, and the component leaks — the most common React bug in review.
Intermediate
Q7
What are the rules of hooks, and why do they exist?
Call hooks only at the top level, never inside a condition or a loop, and only from a component or another hook. React identifies each hook by call order, not by name — so a hook behind an if shifts every later hook by one and state ends up attached to the wrong thing.
Advanced
Q8
What is the difference between useMemo and useCallback?
useMemo caches a computed value; useCallback caches a function identity. They exist for two reasons: skipping a genuinely expensive computation, and keeping a stable reference so a memoised child does not re-render. Both cost memory and complexity, so adding them everywhere makes an app slower and harder to read — measure first.
Advanced
Q9
When would you use useReducer instead of useState?
When the next state depends on the previous one in more than a trivial way, when several values change together, or when the same transitions happen from many places. It moves the logic into one function you can read and test in isolation, instead of spreading it across handlers.
Intermediate
Q10
What is useRef for?
Two things: holding a reference to a DOM node, and holding a mutable value that survives re-renders without triggering one — a timer id, a previous value, a flag. That second use is the one candidates forget, and it is the answer to "how do I keep a value between renders without re-rendering?"
Advanced
Q11
What causes a stale closure in a hook?
An effect or callback captured a variable from an old render and is still reading that old value. It almost always traces to a missing dependency in the array. The fix is either to add the dependency, or to use the updater form — setCount(c => c + 1) — which does not need to read the current value at all.
Routing, forms and everything else
The questions that map to what you built, not what you read.
Intermediate
Q16
How does client-side routing work?
A router listens to the browser's History API, matches the current URL against your route definitions, and renders the matching component — without a request to the server. The server side matters too: it must return the app shell for any path, or a refresh on a deep link 404s.
Intermediate
Q17
How do you build a form in React?
For a small form, controlled inputs backed by one state object, with validation on submit and on blur. For anything larger, a form library — React Hook Form keeps inputs uncontrolled and subscribes to them, so typing in one field does not re-render the whole form, which is the performance problem controlled forms hit as they grow.
Advanced
Q18
What is an error boundary?
A component that catches a render-time error in its subtree and shows a fallback instead of unmounting the whole app. It catches errors during rendering, in lifecycle methods and in constructors — not in event handlers or async code, which need ordinary try/catch.
Advanced
Q19
What is lazy loading, and how do you do it in React?
Splitting code so a route or heavy component downloads only when it is needed, which shrinks the initial bundle. React.lazy(() => import('./Chart')) with a <Suspense> fallback around it. Route-level splitting is where almost all the benefit is.
const Chart = React.lazy(() => import('./Chart'));
<Suspense fallback={<Spinner />}>
<Chart data={data} />
</Suspense>
Intermediate
Q20
What is a custom hook, and when should you write one?
A function starting with use that calls other hooks, extracting stateful logic so it can be reused and tested on its own. Write one when the same combination of state and effects appears in two components — useDebounce, useLocalStorage, useFetch. It shares logic, not state: each caller gets its own.
Intermediate
Q21
How would you test a React component?
React Testing Library, asserting on what a user sees and does — query by role and label, fire events, assert on rendered output — rather than on state or internal method calls. Tests written against internals break on every refactor and pass while the UI is broken, which is the failure mode the library exists to prevent.
Advanced
Q22
What is the difference between server-side rendering and client-side rendering?
With CSR the browser downloads a near-empty page and JavaScript builds the UI — slower first paint, and a crawler sees nothing until the script runs. With SSR the server returns finished HTML that the client then hydrates — a faster first paint and real content for crawlers, at the cost of server work per request. Content that needs to be found by search should be server-rendered.
Advanced
Q23
How do you manage state that many components need?
Start with lifting state up; most apps need nothing more. Use Context for values that are genuinely global and change rarely — theme, user, locale. Reach for a store (Zustand, Redux Toolkit) when many unrelated components read and write the same frequently changing state, because Context re-renders every consumer on each change.