{}The Interview
Handbook

Tracks / React

Advanced patterns, performance & React 19

senior 10 questions · 5 min read performancecontextsuspensersc

Questions in this set 10
  1. 01What is the problem with Context, and how do you fix it?
  2. 02How does reconciliation work, and what is the fiber architecture?
  3. 03Explain Suspense, transitions and the concurrent features.
  4. 04What are React Server Components, and how do they differ from SSR?
  5. 05How do you diagnose a slow React app?
  6. 06Explain custom hooks, and write one.
  7. 07How do you handle errors in React?
  8. 08What is prop drilling, and what are the alternatives — in order?
  9. 09What is hydration, and what causes hydration mismatch errors?
  10. 10What is new in React 19 that changes how you write components?
01

What is the problem with Context, and how do you fix it?

Every consumer of a context re-renders when the provider's value identity changes — there is no selector, so a component reading only theme re-renders when user changes in the same value object.

jsx
// BUG: new object every render -> every consumer re-renders every time
<Ctx.Provider value={{ user, setUser }}>

// Fix 1: memoise the value
const value = useMemo(() => ({ user, setUser }), [user]);

// Fix 2 (better): split contexts by change frequency
<UserCtx.Provider value={user}><DispatchCtx.Provider value={dispatch}>

Splitting state from dispatch is the key pattern: dispatch never changes identity, so components that only dispatch never re-render. For genuinely large shared state with fine-grained reads, use a store with selectors (Zustand, Redux Toolkit, Jotai) — context is a dependency-injection mechanism, not a state manager.

02

How does reconciliation work, and what is the fiber architecture?

React compares element type first: different type → destroy the subtree and rebuild (state is lost); same type → keep the instance, update props, recurse into children matched by key.

Fiber (React 16+) turned the recursive render into a linked-list of units of work that can be paused, resumed and abandoned. That is what makes concurrent rendering possible: React can start rendering a low-priority update, yield to the browser so it can handle a keystroke, and later resume or throw the work away.

A concrete consequence: defining a component inside another component's body creates a new type every render, so React unmounts and remounts the whole subtree, losing state and DOM focus. Never do it.

03

Explain Suspense, transitions and the concurrent features.

  • <Suspense fallback> — declarative loading boundary for lazily-loaded components and, with a supporting data layer, for data.
  • useTransition — marks an update as non-urgent; React keeps the old UI interactive and can interrupt the pending render.
  • useDeferredValue — a lagging copy of a value, for the "type fast, filter a huge list" case.
jsx
const [isPending, startTransition] = useTransition();
const onChange = (e) => {
  setQuery(e.target.value);                              // urgent: the input must feel instant
  startTransition(() => setResults(search(e.target.value))); // non-urgent, interruptible
};

This is the correct answer to "how do you keep an input responsive while rendering 10,000 rows" — with virtualisation (react-window, TanStack Virtual) as the other half.

04

What are React Server Components, and how do they differ from SSR?

SSR renders your client components to HTML on the server, ships the HTML plus the JS bundle, then hydrates — the components run twice. RSCs run only on the server: their code is never sent to the client, they can await data directly, and they stream a serialised description of the UI that the client merges into its tree.

SSR RSC
JS shipped yes none for the server component
can use state/effects yes no
data access via props/loader direct (await db.query(...))
re-renders on interaction yes no — it re-fetches from the server

Rules that follow: a Server Component can render a Client Component but not the reverse (it can pass one through as children); everything crossing the boundary must be serialisable (no functions, no class instances); "use client" marks the boundary and everything imported below it joins the client bundle.

05

How do you diagnose a slow React app?

  1. React DevTools Profiler — record an interaction, read the flame graph, and enable "highlight updates" to see what re-renders. Look for wide bars (many components) and repeated commits.
  2. Ask what changed: is it too many renders, or one expensive render? Different fixes.
  3. Too many renders → stabilise props/context, split contexts, move state down (colocation), lift the expensive subtree into children.
  4. Expensive render → virtualise long lists, memoise real computation, code-split with lazy + Suspense, debounce input-driven work with useDeferredValue.
  5. Slow load → bundle analysis, route-level splitting, defer third-party scripts, check LCP image priority.

The framing that impresses: "I profile first, because the two causes look identical from the outside and have opposite fixes."

06

Explain custom hooks, and write one.

A custom hook is a function starting with use that calls other hooks — it shares logic, not state; each call site gets its own independent state.

jsx
function useDebouncedValue(value, delay = 300) {
  const [debounced, setDebounced] = useState(value);
  useEffect(() => {
    const t = setTimeout(() => setDebounced(value), delay);
    return () => clearTimeout(t);
  }, [value, delay]);
  return debounced;
}

function useLocalStorage(key, initial) {
  const [v, setV] = useState(() => {
    try { const raw = localStorage.getItem(key); return raw ? JSON.parse(raw) : initial; }
    catch { return initial; }
  });
  useEffect(() => {
    try { localStorage.setItem(key, JSON.stringify(v)); } catch {}
  }, [key, v]);
  return [v, setV];
}

Note the lazy initialiser (no localStorage read per render), the try/catch (Safari private mode throws), and the cleanup.

07

How do you handle errors in React?

Error boundaries — class components with getDerivedStateFromError / componentDidCatch (or react-error-boundary). They catch render, lifecycle and constructor errors in their subtree. They do not catch: event handlers, async code, SSR, or errors in the boundary itself. For those, use try/catch and report to Sentry. Place boundaries at route level and around risky widgets so one broken chart does not blank the page.

08

What is prop drilling, and what are the alternatives — in order?

Passing a prop through components that do not use it. Alternatives, cheapest first: composition (pass JSX as children/slots so the data never has to travel), context for genuinely global, rarely-changing values (theme, locale, session), a store with selectors for large or frequently-changing shared state, and a server-cache library (React Query, SWR, RSC) for anything that came from the network — which is most "global state" in practice.

09

What is hydration, and what causes hydration mismatch errors?

Hydration attaches event listeners and builds React's tree over server-rendered HTML, assuming the markup matches exactly. Mismatches come from non-deterministic render: Date.now(), Math.random(), localStorage/window reads, locale-dependent formatting, and invalid nesting (<div> inside <p> — the browser silently fixes the DOM, so it no longer matches). Fixes: render the same thing on both sides and move client-only values into an effect, or use suppressHydrationWarning for genuinely dynamic leaf text like a timestamp.

10

What is new in React 19 that changes how you write components?

  • Actions: useActionState, useFormStatus and <form action={fn}> handle pending/error state for form submissions, including progressive enhancement with Server Actions.
  • use(promise) — read a promise or context inside render, integrating with Suspense.
  • ref as a propforwardRef is no longer needed.
  • The React Compiler — auto-memoises, removing most manual useMemo/useCallback.
  • Document metadata<title>/<meta> render anywhere and hoist to <head>.

Being current here is a cheap differentiator; you do not need to have shipped it, only to know what it replaces.