React front-end interviews: what they assess, and where the format is showing its age
The rubric behind a React interview, how to answer the standard questions (state colocation vs context vs a store like Zustand, re-renders, data fetching, component API design), where to draw the line as a candidate and an interviewer, and why the trivia layer no longer tells you much.
A React interview is a set of proxy questions. Nobody actually cares whether you can recite the four arguments to useReducer. They care whether you can decide where state lives, reason about a tradeoff out loud, design an interface someone else has to use, and debug something you have not seen before. This post covers the questions you will get, how to answer them, where to stop, and why a chunk of the format is now measuring the wrong thing.
What the interview is really scoring#
Behind the questions, most interviewers are grading a handful of things:
- Can you break a vague problem into a component tree and a data flow.
- Do you know where a piece of state should live, and what each option costs.
- Can you name a tradeoff and pick a side with a reason, instead of listing options.
- Can you design a component API that is small, hard to misuse, and does not leak its internals.
- When something breaks, do you have a method, or do you poke at it.
- Do you say what you are assuming, and do you know what you do not know.
Every question below is a way to get at one or more of those. Answer the question in front of you, but aim at the thing it is testing.
State: prop drilling vs context vs a store#
This is the most common conceptual question, usually phrased as "how do you manage state in a large React app". What they want is a decision order and the cost of each step, not a favourite library.
- Local state first.
useStateoruseReducerin the component that owns the interaction. Most state never needs to leave. - Lift to the nearest common ancestor when two siblings need the same value. Pass it down as props.
- Composition before context. Passing a prop through two or three layers is fine. If it is going five layers, restructure with
childrenor slot props so the layers in between do not see it at all. - Context for low-frequency, wide values. Theme, current user, locale, a feature-flag map. Things that are read in many places and change rarely.
- A store (Zustand, Jotai, Redux Toolkit) for state that is cross-cutting and changes often, where context would re-render half the tree on every update.
- A server cache (TanStack Query, RTK Query) for data that comes from an API. That is not application state you own, it is a cache of someone else's state, and it has its own rules.
The context re-render trap
Every component that calls useContext re-renders when the context value changes, by identity. Put a frequently-changing value in one big context and you have built a performance problem. The fixes: split contexts by update frequency, memoise the provider value, or move to a store that lets consumers subscribe to a slice.
Where Zustand fits
Zustand keeps a single store object outside React. Components call useStore(selector) and re-render only when the selected slice changes. There is no provider to wrap the tree, and you can read or write the store from outside a component (an event handler, a socket callback). It is lighter than Redux Toolkit because there are no action types or reducers to declare, and it is top-down where Jotai is bottom-up: one store you carve slices out of, versus many small atoms you compose.
| Tool | Reach for it when | The cost |
|---|---|---|
| useState / useReducer | The state has one owner and a small blast radius | None. This is the default |
| Lift + props | A few components share it and they are close together | A little prop passing |
| Context | Read widely, written rarely (theme, auth, locale) | Every consumer re-renders on any value change |
| Zustand / Jotai | Cross-cutting and frequently updated; you want slice subscriptions | A dependency, and a second place state can live |
| Redux Toolkit | Large app, many contributors, you want strict conventions and devtools | More boilerplate and indirection |
| TanStack Query | The data is from an API | It is a cache, not state: staleness, invalidation, keys |
Re-renders and performance#
The question is usually "this list is janky, what do you do". The shape of a good answer:
- Measure first. React Profiler, "highlight updates", or a trace. Do not optimise from a guess.
- A component re-renders when its parent renders, its state changes, or a context it reads changes. Props being "the same" does not stop it unless the component is wrapped in
React.memo. useMemoanduseCallbackstabilise references somemocan work, and skip expensive recomputation. They are not free, and sprinkling them everywhere is its own problem.- Stable
keys, never the array index for a list that reorders or filters. - For a big list, virtualise. For an expensive derived view,
useDeferredValueso typing stays responsive. - Split a large context, or move that value to a store with selectors.
A deeper treatment of these is in the performance post.
Data fetching#
Expect "fetch this data and render it, now handle the edge cases". The edge cases are the point:
- Loading, error and empty are three different states with three different UIs. Empty is not an error.
- Race conditions. If the user types fast, an earlier response can land after a later one. Ignore stale responses (compare a request id) or abort them with
AbortController. - Waterfalls. Two requests that do not depend on each other should not run in series.
- Caching, dedup and background refetch. This is why
TanStack Queryexists; you do not want to hand-roll it. - Optimistic updates need a rollback path when the request fails.
Component API design#
"Design a <Select>" or "design a <Modal>" is testing whether you can build something other people will use without reading the source. Good instincts:
- Few props, orthogonal. If two props only make sense in certain combinations, you have the wrong shape.
- No boolean explosion.
primary,secondary,dangeras separate booleans becomes avariantunion. - Controlled and uncontrolled. Support
value+onChangefor controlled,defaultValuefor uncontrolled. Do not force the parent to hold state it does not care about. - Composition over configuration. A
<Modal>takeschildren, not abodyTextandfooterButtonsarray. Compound components (<Tabs>,<Tabs.Tab>) when the layout needs to be flexible. - Forward the ref, spread the rest. Let callers reach the underlying element and pass
aria-*,data-*,id. - Sensible defaults. The zero-config version should be correct and accessible.
API design comes up for HTTP contracts too. If asked, the same principles apply: predictable shapes, every field always present with a typed empty value, versioned, errors that say what to do.
The front-end system design round#
"Build a typeahead search" or "an infinite feed" or "a data grid with inline edit". They are not looking for finished code. Structure the answer:
- Clarify scope. How many rows, does it need offline, is SEO in play, what devices.
- Component tree and where each piece of state lives.
- Network strategy: debounce, cancel in-flight, cache, page size, prefetch.
- Rendering strategy: virtualisation, memoisation boundaries, skeleton states.
- Accessibility: roles, keyboard model, focus management, announcements.
- Edge cases: empty, error, slow network, very long content, rapid input.
- Then iterate on whichever part the interviewer pushes on.
Where to draw the line as a candidate#
- Clarify scope and constraints before you write anything. An hour spent solving the wrong problem scores badly.
- Say your assumptions out loud so the interviewer can correct them early.
- "I do not remember the exact signature, I would check the docs" is a fine answer. Pretending is not.
- Do not gold-plate. Get the core working, then say what you would add with more time.
- When you are stuck, narrate your debugging method. The method is what is being scored, not the speed of the fix.
Where to draw the line as an interviewer#
- Stop testing memorised trivia: event-loop microtask ordering, CSS specificity puzzles, the exact
useMemosyntax. It correlates with recent cramming, not with being good at the job. - Do not penalise a candidate for not knowing an API name. Tell them the name and see what they do with it.
- "Implement debounce from scratch" tells you one thing, once. You do not need it in three rounds.
- If your take-home would take a strong engineer more than two hours, it is filtering for free time, not skill.
The part that is showing its age#
Most of what a coding round measures directly, syntax recall, boilerplate, a small util, a first-draft component, is now something an agent produces in seconds and usually gets right.
That does not make the skills worthless, but it does mean a whiteboarddebounce or a from-memory reducer is a weak signal. It tells you the candidate revised. It does not tell you they can carry a feature from a fuzzy ask to production.
What still separates people, and is hard to fake:
- Judgement on tradeoffs when there is no clean answer.
- Reading unfamiliar code quickly and correctly.
- Debugging with incomplete information and a method.
- Spotting where generated code is subtly wrong: an off-by-one, a missing cleanup, a race, an accessibility regression, a security hole.
- Decomposing a vague problem into a plan.
- Designing an interface other people will build on.
- Driving an agent well: scoping the task, writing the prompt, reviewing the diff, knowing when to take the keyboard back.
What a more useful interview looks like now#
- Agent-assisted ticket. Give the candidate an agent and a deliberately underspecified task. Watch them scope it, prompt it, review what it produces, and catch the bug it introduced.
- Review a bad PR. Hand them a diff an agent wrote that has three real problems. Ask them to find and explain them.
- Code reading. Here is an unfamiliar 400-line module. Explain what it does, where it would break, and what you would change first.
- A design conversation with no coding. Talk through state ownership, an API shape, a migration order. Pure tradeoff reasoning.
- Keep one small hand-coded exercise as a sanity check that they can actually write code, not as the whole loop.