React Interview: Rendering Model, Keys & Component Design
The theory round done properly — how React actually renders and reconciles, why keys matter, props vs state, controlled vs uncontrolled inputs, lifting state, and the composition patterns interviewers ask you to name.
4 sections · ~32 min · 5-question quiz (pass ≥ 70%)
1What Actually Happens When State Changes
The question "what happens when you call setState?" separates people who have read the docs from people who have debugged React.
The cycle:
- Trigger — a state update is queued. React does not apply it immediately.
- Render — React calls your component function to produce a new element tree (plain JS objects, not DOM). This must be pure: same props and state in, same JSX out, no side effects.
- Reconcile (diff) — React compares the new tree with the previous one.
- Commit — React applies only the actual differences to the real DOM, then runs layout effects, paints, then runs
useEffectcallbacks.
Why a virtual DOM at all? Not because it is faster than a hand-written DOM update — it is not. It is because it lets you write declarative UI ("here is what the screen should look like for this state") and lets React work out the minimal set of mutations.
The reconciliation rules you must be able to state:
- Different element type at the same position → destroy and rebuild the whole subtree, losing all its state.
<div>becoming<span>, or a component swapping to a different component, unmounts everything below. - Same type → keep the DOM node, update the changed attributes, recurse into children.
- Position in the tree defines identity, unless you provide a
key.
That third rule explains a bug people hit constantly:
// BAD — a new component type is created on every render of Parent,
// so React unmounts and remounts Child every time. All its state is lost.
function Parent() {
function Child() { return <input />; }
return <Child />;
}
Re-render ≠ DOM update. A component re-rendering means React called your function again. If the output is unchanged, the DOM is untouched. Interviewers like to check that you know re-renders are cheap-ish and that premature memoization is a real cost.
2Keys: The Question Everyone Gets Half Right
"Why do you need a key when rendering a list?" Most candidates say "for performance". That is the smaller half of the answer.
Keys give elements a stable identity across renders. Without them, React matches list children by index — so if the list reorders, React thinks the contents changed rather than the positions, and DOM state attached to those positions (input values, focus, scroll, CSS transition state, component state) stays behind with the wrong item.
The bug, concretely:
{items.map((item, i) => <TodoRow key={i} todo={item} />)}
Delete the first item. Every remaining row shifts down one index. React sees "row 0's props changed" rather than "row 0 was removed", so it reuses row 0's DOM node and component state for what is now a different todo. A half-typed input or a checked checkbox follows the position, not the item.
{items.map((item) => <TodoRow key={item.id} todo={item} />)} // correct
Rules:
- Use a stable, unique ID from your data. A database id is ideal.
- Index keys are acceptable only when the list is static, never reordered, never filtered, and items have no internal state.
- Never use
Math.random()orDate.now()— a fresh key every render forces a full unmount/remount of every row. - Keys must be unique among siblings, not globally.
The advanced follow-up: you can use a key deliberately to force a remount and reset state — <Profile key={userId} /> gives each user a fresh component instead of a stale one. Volunteering that shows you understand keys as an identity mechanism rather than a lint rule.
3Props vs State, and Controlled vs Uncontrolled
Props are read-only inputs from the parent. State is data a component owns and can change. If a value never changes, it is neither — it is a constant.
Never mutate props or state directly. React compares by reference, so an in-place mutation produces no re-render:
items.push(newItem); setItems(items); // BAD — same reference, no render
setItems([...items, newItem]); // GOOD — new array
user.name = "Ada"; setUser(user); // BAD
setUser({ ...user, name: "Ada" }); // GOOD
setItems(items.map(i => i.id === id ? { ...i, done: true } : i)); // update one
setItems(items.filter(i => i.id !== id)); // remove one
Nested updates need spreads at every level you change: { ...state, address: { ...state.address, city } }.
Controlled inputs — React state is the single source of truth:
const [email, setEmail] = useState("");
<input value={email} onChange={(e) => setEmail(e.target.value)} />
Use these when you need live validation, formatting, conditional disabling, or to reset the field programmatically.
Uncontrolled inputs — the DOM holds the value; you read it via a ref:
const inputRef = useRef(null);
<input defaultValue="" ref={inputRef} /> // defaultValue, not value
// later: inputRef.current.value
Cheaper (no re-render per keystroke) and fine for simple submit-only forms or file inputs (which are always uncontrolled).
The classic warning — "A component is changing an uncontrolled input to be controlled" — means value started as undefined and later became a string. Initialise to "", never undefined.
Lifting state up: when two siblings need the same data, move it to their closest common parent and pass the value down plus a setter callback up. Being able to say "I would lift it to the nearest common ancestor, and reach for context only when the prop drilling spans several layers" is the expected answer.
4Composition Patterns and Component Design
Composition over inheritance. React has no component inheritance — you compose. Know these by name:
children / slots — the default and best answer to "how do I make a reusable wrapper?"
function Card({ title, actions, children }) {
return (
<section className="card">
<header>{title}{actions}</header>
<div>{children}</div>
</section>
);
}
Custom hooks — the modern way to share logic (as opposed to markup). This is what replaced mixins, HOCs, and render props for most cases.
Higher-order component (HOC) — a function taking a component and returning a wrapped one (withAuth(Page)). Mostly legacy now; be able to recognise it.
Render props — pass a function as a child so the caller controls rendering. Also largely superseded by hooks.
Compound components — related components sharing implicit state via context: <Tabs><Tab/><TabPanel/></Tabs>.
Presentational vs container — split "how it looks" from "where data comes from". Still useful vocabulary, and it maps neatly onto Server vs Client Components.
Practical design rules interviewers listen for:
- Prefer many small, single-purpose components; a 300-line component with six
useStatecalls is a refactor waiting to happen. - Derive, don't duplicate. If a value is computable from existing state, compute it during render — don't add another
useStateand auseEffectto sync it. Redundant state is the #1 React design smell. - Push state as far down the tree as it will go; lift only when genuinely shared.
React.Fragment(<>...</>) avoids junk wrapper divs.- Conditional rendering: prefer
cond ? <A/> : nullovercount && <A/>— a0renders as a literal "0" on screen.