Interview Bank · 2026
Components, hooks, the virtual DOM, and the gotchas that separate "I've used React" from "I think in React." Say each answer aloud before you reveal it.
Click a card. Answer first, in one breath, then reveal.
useState do?useEffect do?key prop?value={x} plus an onChange that updates it.React.createElement calls — markup and logic in one place.useMemo vs useCallback?useMemo caches a computed value; useCallback caches a function reference. Both skip work when deps are unchanged.Calling a state setter (e.g. setCount) tells React the component is "dirty." React re-runs the component function to produce a new virtual-DOM tree, diffs it against the previous one, and applies the minimal set of changes to the real DOM. You never touch the DOM yourself — you change state and describe the result.
useEffect and its dependency array (and cleanup).The effect runs after render. The dependency array controls when: [] = once on mount; [a, b] = whenever a or b changes; omitted = after every render. Returning a function gives you cleanup — React runs it before the next effect and on unmount (unsubscribe, clear a timer, abort a fetch). List every value the effect reads, or you'll get stale data.
When two siblings need the same data, you move that state to their nearest common parent and pass it down as props (plus a callback to update it). The parent becomes the single source of truth. It's the standard fix for "these two components must stay in sync."
Controlled: React state holds the value (value + onChange) — you can validate, transform, and reset easily. Uncontrolled: the DOM holds the value and you read it with a ref when needed. Controlled is the default recommendation; uncontrolled is handy for simple or third-party-managed fields.
During diffing, keys tell React which list item is the same across renders. With good keys React moves/updates existing nodes; with bad keys it tears down and rebuilds, losing focus and state and slowing things down. Keys must be stable and unique — use an id from your data, not the array index.
useContext for?It reads a value from a Context.Provider higher in the tree without passing props through every level ("prop drilling"). Good for app-wide things: theme, current user, auth. Note any consumer re-renders when the context value changes, so keep volatile state out of broad contexts.
A function starting with use that calls other hooks to package reusable stateful logic — e.g. useFetch, useDebounce. It shares behaviour, not markup, and keeps components lean. It's the React equivalent of the reusable jQuery plugins you used to write — logic you drop into many places.
useRef vs state?useRef holds a mutable value that persists across renders but does not trigger one when it changes — for DOM nodes, timer ids, or "previous value" bookkeeping. State is for values that should re-render the UI. Rule of thumb: if changing it should update what the user sees, use state; otherwise a ref.
React keeps a lightweight in-memory representation of the UI. On a state change it builds a new tree and runs reconciliation — a diff against the old tree. It assumes elements of the same type are the same, recurses into children, and computes the minimal DOM operations to apply. This batched, targeted patching is why you rarely touch the DOM directly.
Without keys, React diffs list children by position, so inserting at the top makes it think every item changed. Keys give each item an identity, so React can match "item 7 is still item 7" and just reorder or update it — preserving its DOM node, focus, and internal state. They are the hint that makes list diffing correct and fast.
In jQuery you wrote imperative steps: "find this node, toggle that class, append a row." In React you write declarative UI: "given this state, here's what the screen should look like," and React figures out the DOM mutations. You describe the destination, not the turn-by-turn directions — fewer bugs from the DOM drifting out of sync with your data.
Data flows down the tree via props; events flow up via callbacks. A child can't reach up and mutate its parent's state — it calls a function the parent gave it. This single direction makes the app predictable: to find why something changed, you trace upward to where the state lives.
React tracks hooks by call order, not by name — the first useState is "slot 1" every render. If you put a hook inside an if or a loop, the order can change between renders and React maps your state to the wrong slot. Calling them unconditionally at the top keeps the order stable, which is the whole reason the Rules of Hooks exist.
If a value can be computed from existing props/state, compute it during render instead of storing it in its own useState. Duplicating it means two sources of truth that drift out of sync (you update one and forget the other). Keep the minimal state; derive the rest. useMemo only if the computation is genuinely expensive.
useEffect? trickyAn effect "captures" the variables from the render it ran in. If you set up a one-time effect ([]) that reads count, it forever sees the first count, even as state updates. Fix: add the value to the deps so the effect re-subscribes, or use the functional setter setCount(c => c + 1) which never needs the current value.
Omit a dependency and the effect keeps using an old value (stale). But add one carelessly — like a function or object recreated every render, or a value the effect itself updates — and the effect re-runs every render, looping forever. Fix the loop by memoising the dependency (useCallback/useMemo) or restructuring so the effect doesn't set the very state it depends on.
key={index} an antipattern? trickyWhen the list reorders, inserts, or deletes, indexes shift, so React matches the wrong items — causing wrong content, lost input focus, and checkbox state landing on the wrong row. Indexes are only safe for a static, append-only list. Use a stable id from the data instead.
No. if (x) useState() or a hook inside a for breaks the call-order contract React relies on, and the linter/runtime will error. Put the condition inside the hook instead (e.g. useEffect(() => { if (x) {...} })), or split into separate components.
setState asynchronous / batched? trickySetters schedule an update; they don't mutate immediately, and React batches multiple updates into one re-render for performance. So reading state right after setting it gives the old value, and setCount(count+1) twice only adds one. Use the functional form setCount(c => c + 1) when the next value depends on the previous.
useEffect run twice in StrictMode? trickyIn development only, StrictMode intentionally mounts, unmounts, and remounts components to surface effects that aren't cleaned up properly. It does not happen in production. The fix is almost always to return a proper cleanup function — not to disable StrictMode.
Actions plus useActionState streamline form submission, pending states, and errors; useOptimistic shows an instant optimistic UI while a mutation is in flight; the new use() hook reads a promise or context (and can be called conditionally, unlike other hooks); and ref is now a normal prop — no more forwardRef boilerplate. Mentioning these signals you're current.
An optimising compiler that auto-memoises components and values at build time, so React skips re-rendering and recomputing things that didn't change — without you hand-writing useMemo/useCallback/memo everywhere. It reduces a whole category of manual performance work; you still understand those hooks, but lean on them far less.
React Server Components are now a first-class concept: components that render on the server, ship zero JS for themselves, and can fetch data directly — reducing bundle size and round-trips. You meet them mainly through a framework, so the deep dive lives in the Next.js bank; here, just know what they are and why they exist.
if, no loop — React counts them by order.
useEffect to stream answers, custom useFetch hook, and keys on the message list." Concrete beats abstract.