QuizCluster
Frontend EngineeringFrontend Developer to Senior/Staff React Engineer17 min read

React Interview Preparation: React 19 & Next.js Guide

From Fiber Internals and Hook Pitfalls to Server Components and the App Router Rendering Pipeline

Maya Chen
Senior Frontend Architect & React Core Contributor
11+ Years Building Large-Scale React & Next.js Platforms
Prep Timeline
5 to 7 Weeks
Format
Hooks & Component Design, Rendering Internals, State Architecture, Next.js System Design
Conversion
+80% Frontend Technical Pass Rate
React Interview Preparation: React 19 & Next.js Guide
Executive Summary & Key Takeaways

What You Must Master to Clear This Track

  • Understand the Fiber reconciler's two-phase model (interruptible render phase, synchronous commit phase) and how priority lanes let React interrupt low-priority work for urgent updates.
  • Master React 19's Actions, useOptimistic, useFormStatus, and the use() hook for reading promises and context conditionally inside render.
  • Know exactly when useEffect fires relative to browser paint versus useLayoutEffect, and how to avoid stale closures and dependency array bugs.
  • Be able to explain SSR, SSG, ISR, and React Server Components trade-offs, plus what actually causes hydration mismatches in the App Router.
  • Choose the right state tool for the job — local state, lifted state, Context, or an external store — instead of defaulting to global state for everything.
Structured Preparation Timeline

Step-by-Step Study Plan

Follow this sequential roadmap designed to take you from core foundations to advanced architecture and mock interviews.

Phase 1 (Weeks 1-2)

JSX Mental Model, Component Design & Hook Internals

React Fundamentals & Hooks Mastery

Rebuild your mental model of JSX-to-createElement compilation, the unidirectional data flow, and go deep on useState, useEffect, useRef, and useReducer semantics.

Key Milestones
  • Explain why JSX compiles to React.createElement/jsx-runtime calls and how that produces the element tree React diffs.
  • Trace exactly when useEffect callbacks and cleanups fire relative to render and browser paint.
  • Build 3-4 custom hooks (useDebouncedValue, useFetch with cancellation, useLocalStorage) from scratch.
Recommended Actions
  • Enable and never silence eslint-plugin-react-hooks exhaustive-deps warnings during practice.
  • Rewrite one class-component lifecycle example as hooks to internalize the componentDidMount/DidUpdate/WillUnmount mapping.
Phase 2 (Weeks 3-4)

Fiber, Concurrent Rendering, React 19 Features & State Strategy

Rendering Internals, React 19 & State Architecture

Study the Fiber tree, render vs commit phases, priority lanes, and React 19's Actions/useOptimistic/use() hook, then map out when to reach for Context vs an external store.

Key Milestones
  • Diagram the render phase (work loop, diffing) and commit phase (DOM mutation, layout effects) as two distinct passes.
  • Implement a form using a Server Action, useOptimistic, and useFormStatus end to end.
  • Compare re-render behavior of Context, Zustand/Redux, and prop drilling on the same feature.
Recommended Actions
  • Profile a component tree with React DevTools Profiler to see which components re-render and why.
  • Practice explaining priority lanes (Sync, Input Continuous, Transition) with a concrete UI example like a search-as-you-type input.
Phase 3 (Weeks 5-6)

Rendering Strategies, Hydration, Caching Layers & Live Practice

Next.js App Router, Performance & Mock Interviews

Master SSR/SSG/ISR/RSC trade-offs, the App Router's four caching layers, hydration mismatch debugging, and rehearse full-length React system design interviews.

Key Milestones
  • Explain the RSC request/render/hydration pipeline end to end, including what streams and what hydrates.
  • Debug at least 2 real hydration mismatch scenarios (Date.now(), window checks, browser extensions injecting DOM).
  • Complete 4-5 mock interviews covering hook debugging, a component design exercise, and a Next.js rendering-strategy scenario.
Recommended Actions
  • Always state which parts of a feature you would render on the server vs mark 'use client' before writing code.
  • Time-box whiteboard component design answers to 20 minutes, leaving room for follow-up performance questions.
Deep-Dive Architecture & Concepts

1. React 19: Actions, the use() Hook & Server Components

React 19 formalizes patterns the community had been hand-rolling for years — pending/optimistic UI, form handling, and server/client composition — into first-class primitives. Interviewers now expect you to know not just hooks, but this new mental model.

Actions & Transitions

An Action is any async function passed to a form's action prop, useTransition, or a Server Action. React automatically tracks its pending state, handles errors, and keeps the UI responsive by treating the update as a non-urgent transition.

The use() Hook

Unlike other hooks, use() can be called conditionally and inside loops. It unwraps a Promise (suspending the component until it resolves) or reads a Context value, unifying data-fetching and context consumption under one API.

useOptimistic & useFormStatus

useOptimistic renders a temporary UI state that reverts or reconciles once the underlying async Action settles. useFormStatus lets nested components read a parent form's pending/data state without prop drilling.

React Server Components (RSC)

Server Components render entirely on the server (or at build time), never ship their JS to the client, and can read databases/secrets directly. They compose with Client Components ('use client') by passing serializable props and children down.

React 19 Server Action + useOptimistic Form
tsx
// app/actions.ts
  "use server";
  
  import { db } from "@/lib/db";
  import { revalidatePath } from "next/cache";
  
  export async function addTodo(formData: FormData) {
    const text = formData.get("text") as string;
    await db.todo.create({ data: { text, completed: false } });
    revalidatePath("/todos");
  }
  
  // app/todo-form.tsx
  "use client";
  
  import { useOptimistic, useRef } from "react";
  import { addTodo } from "./actions";
  
  type Todo = { id: string; text: string; completed: boolean };
  
  export function TodoForm({ todos }: { todos: Todo[] }) {
    const [optimisticTodos, addOptimisticTodo] = useOptimistic(
      todos,
      (state, newText: string) => [
        ...state,
        { id: `temp-${Date.now()}`, text: newText, completed: false },
      ]
    );
    const formRef = useRef<HTMLFormElement>(null);
  
    return (
      <form
        ref={formRef}
        action={async (formData) => {
          const text = formData.get("text") as string;
          addOptimisticTodo(text);
          formRef.current?.reset();
          await addTodo(formData);
        }}
      >
        <input name="text" placeholder="Add a task" />
        <button type="submit">Add</button>
        <ul>
          {optimisticTodos.map((todo) => (
            <li key={todo.id}>{todo.text}</li>
          ))}
        </ul>
      </form>
    );
  }
Why it matters: The Server Action mutates data and revalidates the route, while useOptimistic instantly renders the new item before the network round-trip resolves, then reconciles with real server state once addTodo completes — no manual pending-state booleans required.
Interviewer Insights & Pro Tips
  • In interviews, explicitly contrast useOptimistic with manually managing a 'pending' boolean in useState — the interviewer is testing whether you know React now owns that lifecycle.
  • Be ready to explain that use() is not a replacement for useEffect-based data fetching in Client Components; it is primarily meant to consume promises created by Server Components or a cache-aware fetcher.
Red Flags & Common Pitfalls
  • Calling use() with a promise created fresh on every render (instead of one from a stable cache/Server Component boundary) causes an infinite re-suspend loop.
  • Marking an entire page 'use client' just because one small widget needs interactivity, which forfeits the zero-JS benefit of Server Components for the rest of the tree.
Deep-Dive Architecture & Concepts

2. The Fiber Reconciler & Render-Commit Lifecycle

Since React 16, the Fiber architecture underlies every render. Interviewers use this topic to separate candidates who memorized hook syntax from those who understand what React is actually doing on every update.

What a Fiber Is

A Fiber is a plain JS object representing one unit of work for a component instance — it tracks type, props, state, and pointers (child/sibling/return) forming a linked-list tree, which lets React pause, resume, or abort work instead of relying on the call stack.

Double Buffering

React keeps two Fiber trees: 'current' (what's on screen) and 'work-in-progress' (being built). At the end of a successful commit, React simply swaps which tree is current — an O(1) pointer flip rather than rebuilding the UI.

Priority Lanes & Concurrent Rendering

Updates are assigned to lanes (Sync, Input Continuous, Default, Transition, Idle). A urgent lane (e.g. a keystroke) can interrupt in-progress low-priority render work (e.g. a useTransition-wrapped search-results render), keeping input responsive.

Reconciliation & Keys

During diffing, React compares Fibers of the same type at the same position by key. Matching keys let React reuse the DOM node and its state; mismatched or missing keys (like array indexes on reordered lists) cause unnecessary unmounts/remounts.

React Render-Commit Pipeline

How a single setState call travels from scheduling through DOM mutation to the browser paint.

1
Schedule Update
setState/dispatch marks a Fiber as dirty and assigns it a priority lane; React's scheduler queues the work.
2
Render Phase (interruptible)
React walks the tree building a work-in-progress Fiber tree, calling function components and diffing against the current tree by key. Higher-priority work can pause this phase.
3
Reconciliation
The diff produces an effect list tagging Fibers as Placement, Update, or Deletion — describing exactly which DOM mutations are needed.
4
Commit Phase (synchronous)
React applies all DOM mutations in one uninterruptible pass, fires useLayoutEffect callbacks synchronously, then swaps the work-in-progress tree to become current.
5
Paint & Passive Effects
The browser paints the updated DOM, and React schedules useEffect callbacks to run asynchronously right after paint.
Interviewer Insights & Pro Tips
  • When asked 'why is useLayoutEffect sometimes necessary,' answer with the pipeline above: it runs synchronously in the commit phase before paint, so it can measure/mutate the DOM without a visible flicker — useEffect cannot make that guarantee.
Red Flags & Common Pitfalls
  • Assuming setState is always synchronous — inside React event handlers, React 18+ automatically batches multiple updates into a single render pass, even across Promise/setTimeout boundaries.
  • Treating the render phase as side-effect-safe for mutations; because it can be paused, aborted, or re-run, function components must stay pure during render.
Deep-Dive Architecture & Concepts

3. Hooks Deep-Dive: useEffect Timing, Custom Hooks & Pitfalls

Hook questions are the most common part of any React interview because they expose whether a candidate understands closures, cleanup semantics, and dependency correctness — not just hook names.

useEffect vs useLayoutEffect Timing

useEffect callbacks run asynchronously after the browser has painted, avoiding blocking visual updates. useLayoutEffect runs synchronously immediately after DOM mutations but before paint, used for measuring layout or preventing a flash of incorrect content.

Stale Closures

A hook callback captures the props/state values from the render it was created in. If a dependency array omits a reactive value, the callback keeps referencing that render's stale value on every subsequent invocation.

Custom Hooks as Composable Logic

A custom hook is just a function starting with 'use' that calls other hooks; it lets you extract and reuse stateful logic (data fetching, subscriptions, debouncing) across components without changing the component tree, unlike HOCs or render props.

useReducer for Complex State Transitions

When multiple pieces of state update together in response to the same events (a multi-step form, an undo/redo stack), useReducer centralizes transition logic and makes state changes easier to test and trace than scattered useState calls.

Custom useFetch Hook with Cancellation & Race-Condition Safety
tsx
import { useEffect, useState } from "react";
  
  interface FetchState<T> {
    data: T | null;
    loading: boolean;
    error: Error | null;
  }
  
  function useFetch<T>(url: string): FetchState<T> {
    const [state, setState] = useState<FetchState<T>>({
      data: null,
      loading: true,
      error: null,
    });
  
    useEffect(() => {
      const controller = new AbortController();
      setState((prev) => ({ ...prev, loading: true, error: null }));
  
      fetch(url, { signal: controller.signal })
        .then((res) => {
          if (!res.ok) throw new Error(`Request failed: ${res.status}`);
          return res.json() as Promise<T>;
        })
        .then((data) => setState({ data, loading: false, error: null }))
        .catch((error: Error) => {
          if (error.name !== "AbortError") {
            setState({ data: null, loading: false, error });
          }
        });
  
      // Cleanup: aborts the in-flight request if url changes or component unmounts
      return () => controller.abort();
    }, [url]);
  
    return state;
  }
Why it matters: The AbortController cleanup prevents two classic bugs at once: setting state on an unmounted component, and a race condition where a slower earlier request resolves after a faster later one and overwrites fresher data.
Interviewer Insights & Pro Tips
  • When explaining a dependency array bug, name the mechanism precisely: 'the effect closed over the render-N value of count because count was omitted from the deps array,' not just 'it was stale.'
  • Prefer the functional updater form, setCount(c => c + 1), when a new state value depends on the previous one — it removes the need to list the state variable itself as a dependency.
Red Flags & Common Pitfalls
  • Returning a cleanup function that references the wrong closure, unsubscribing from a newer subscription instead of the one the effect actually created.
  • Putting an object or array literal directly in a dependency array — it is a new reference every render, causing the effect to re-run on every single render.
Deep-Dive Architecture & Concepts

4. State Management & Next.js App Router Rendering Strategies

Modern React interviews increasingly test system-design-style judgment: choosing the right state tool for a feature, and reasoning about where in the SSR/SSG/ISR/RSC spectrum a page should live in the Next.js App Router.

State Management Spectrum

Local component state (useState/useReducer) for ephemeral UI, lifted state or composition for a few nearby components, Context for low-frequency cross-cutting values (theme, auth session), and an external store (Zustand, Redux Toolkit, Jotai) with selectors for complex or high-frequency global state.

SSR vs SSG vs ISR vs RSC

SSR renders per request for freshness; SSG pre-renders at build time for maximum speed on content that rarely changes; ISR statically serves pages but revalidates them on an interval or on-demand; RSC is a rendering model (not tied to timing) where Server Components stream serialized UI and ship zero JS to the client by default.

Hydration & Its Failure Modes

Hydration attaches event listeners and reconciles React's virtual tree against the server-rendered HTML without re-creating DOM nodes. A mismatch (different output on server vs first client render, e.g. from Date.now() or window checks) forces React to discard and re-render, or throw a hydration error.

App Router Caching Layers

Four distinct caches operate together: Request Memoization (dedupes identical fetch calls within one render), the Data Cache (persists fetch results across requests/deploys), the Full Route Cache (caches rendered HTML/RSC payload at build time), and the Router Cache (client-side, caches visited route segments for instant back/forward navigation).

Next.js App Router Request-to-Hydration Pipeline

How a request becomes interactive UI in an app mixing Server and Client Components.

1
Request Hits the Server
Next.js matches the route segment tree and begins rendering Server Components, fetching data directly (DB calls, internal APIs) with no client bundle involved.
2
RSC Payload Generation
Server Components render to a compact serialized RSC payload; any 'use client' boundaries are left as placeholders referencing their client bundle.
3
HTML Streaming to Client
The server streams initial HTML (and the RSC payload) to the browser incrementally, using Suspense boundaries so slower data-dependent sections arrive later without blocking the shell.
4
Client Bundle Hydration
React loads the JS for Client Component boundaries and hydrates them, attaching event listeners and reconciling against the server HTML without discarding existing DOM nodes.
5
Interactive & Router-Cached
The page becomes fully interactive; the visited route segment is stored in the client Router Cache so subsequent navigations to it are instant.
Interviewer Insights & Pro Tips
  • When asked to pick a rendering strategy, always tie the answer to data freshness requirements and traffic pattern, e.g. 'a product page with 10k SKUs updated hourly is a strong ISR candidate with a 3600s revalidate.'
  • If asked how to fix a hydration mismatch caused by browser-only APIs, mention moving the value into useEffect/state (so it renders only after mount) rather than reaching for suppressHydrationWarning as a first resort.
Red Flags & Common Pitfalls
  • Reaching for Redux/Context to store data that is really server-owned (a fetched list, a user record) instead of using a cache-aware fetcher or Server Components, duplicating a source of truth.
  • Assuming 'use client' opts an entire subtree out of streaming/SSR — it only means that component (and its children) hydrate and run in the browser; it is still server-rendered for the initial HTML.
Real-World Example

Migrating a Legacy CRA Fintech Dashboard to the Next.js App Router

A fintech analytics team maintained a Create React App dashboard that shipped roughly 1.4MB of JavaScript before a single chart rendered, causing slow, sluggish load times for account managers checking overnight metrics from constrained office networks.

  • 1Audited the component tree to separate data-fetching and presentational logic from genuinely interactive widgets (date pickers, drill-down charts).
  • 2Migrated top-level dashboard routes to the Next.js App Router, converting static shells and KPI tiles into Server Components that fetch metrics directly from internal APIs.
  • 3Isolated interactive pieces into small 'use client' leaf components instead of marking entire pages client-side, keeping most of the tree server-rendered with zero shipped JS.
  • 4Replaced a monolithic Redux store with React Query for server-owned cache state, keeping Zustand only for local UI state like open panels and selected filters.
  • 5Added per-widget Suspense boundaries with loading.tsx skeletons so above-the-fold KPIs streamed in immediately while slower chart queries resolved separately.
Outcome: Client JavaScript bundle size dropped by 61% and first meaningful paint improved from 3.8s to 1.2s on a throttled connection, with no loss of interactivity.
Real-World Interview Questions

Top Must-Know Interview Questions & Model Answers

Hooks & LifecycleMust-Know

Q1: What are the Rules of Hooks, and why must hooks be called unconditionally at the top level of a component?

Executive Answer:Hooks must be called in the same order on every render because React tracks each hook's state using its call index in an internal linked list, not by name.
Deep Dive Analysis:
  • React associates hook state with a Fiber-level array/linked-list, matching the Nth useState call in render 1 to the Nth useState call in render 2.
  • Calling a hook inside a conditional, loop, or after an early return can shift that index between renders, silently attaching the wrong stored state to the wrong hook call.
  • eslint-plugin-react-hooks statically enforces this rule and also validates exhaustive dependency arrays.
Interviewer Takeaway: If conditional logic is needed, put the condition inside the hook (e.g. inside the effect body) rather than around the hook call itself.
Hooks & LifecycleMedium

Q2: Explain why useEffect runs after paint but useLayoutEffect runs before paint, and when that distinction actually matters.

Executive Answer:useEffect is scheduled asynchronously after the commit phase and browser paint so it never blocks visual updates; useLayoutEffect runs synchronously during the commit phase, before the browser paints.
Deep Dive Analysis:
  • Use useLayoutEffect when you must measure or mutate the DOM (e.g. reading an element's bounding box to position a tooltip) and need that mutation visible in the very first paint, avoiding a flicker.
  • Defaulting to useLayoutEffect everywhere can block paint on slow computations, hurting perceived performance; it should be the exception, not the default.
Interviewer Takeaway: Reach for useLayoutEffect only for synchronous DOM reads/writes that must not flash; use useEffect for everything else (subscriptions, fetching, logging).
Fiber & ReconciliationHard

Q3: What is a Fiber, and how does the Fiber architecture differ from React's old stack-based reconciler?

Executive Answer:A Fiber is a JS object representing one unit of work with explicit child/sibling/return pointers, letting React pause and resume rendering; the old stack reconciler used the native call stack, which could not be interrupted.
Deep Dive Analysis:
  • The pre-Fiber (React 15) reconciler recursively walked the tree synchronously via the JS call stack, so a large update could block the main thread until fully complete.
  • Fiber represents the tree as a linked list traversable iteratively, so React's scheduler can yield back to the browser mid-render and resume later — the foundation for concurrent features.
Interviewer Takeaway: Fiber turns rendering from an unbreakable recursive call into a resumable unit-of-work loop, enabling priority-based interruption.
React 19 FeaturesMust-Know

Q4: What problem does the new use() hook solve, and how is it different from calling a normal hook conditionally?

Executive Answer:use() lets a component read the value of a Promise or Context and can be called conditionally/in loops, because it is not a stateful hook itself — it reads from a resource React suspends on.
Deep Dive Analysis:
  • Passing a promise to use() suspends the component (triggering the nearest Suspense boundary) until it resolves, then returns the resolved value directly in render.
  • Because use() doesn't rely on call-order-based state like useState, React explicitly permits it inside if-statements and loops, unlike every other built-in hook.
Interviewer Takeaway: use() unifies reading promises and context into one conditional-safe API, but it is not a replacement for useEffect-driven client-side fetching triggered by user interaction.
React 19 FeaturesMedium

Q5: What is a Server Action, and how does progressive enhancement work for forms built with React 19?

Executive Answer:A Server Action is an async function marked 'use server' that runs only on the server; when passed to a form's action prop, the form submits and works even before JavaScript loads, then upgrades to a client-driven fetch once hydrated.
Deep Dive Analysis:
  • Without JS, the browser performs a normal HTML form POST that the framework routes to the Server Action.
  • Once hydrated, React intercepts the submission, calls the action via a fetch-like RPC, and can combine it with useOptimistic/useFormStatus for instant feedback.
Interviewer Takeaway: Server Actions collapse 'API route + client fetch + form handler' into one function, while still degrading gracefully without JS.
Fiber & ReconciliationHard

Q6: Explain React's concurrent rendering and the priority lanes model in your own words.

Executive Answer:Concurrent rendering lets React work on multiple updates with different urgency at once, assigning each to a priority lane so urgent updates (typing) can interrupt and re-order ahead of less urgent ones (a large list re-render wrapped in useTransition).
Deep Dive Analysis:
  • Lanes include Sync (must flush immediately, e.g. a controlled input keystroke), Input Continuous, Default, and Transition (marked via startTransition/useTransition, allowed to be deferred and even discarded if superseded).
  • This is what makes React 'interruptible': the render phase for a low-priority lane can be paused mid-way and abandoned in favor of finishing a higher-priority lane first.
Interviewer Takeaway: Wrap non-urgent state updates (large list filtering, tab switches with heavy content) in startTransition so typing and clicks stay responsive.
Next.js App RouterMust-Know

Q7: What is the difference between SSR, SSG, ISR, and RSC in the Next.js App Router?

Executive Answer:SSR, SSG, and ISR describe when/how often a page's HTML is generated (per-request, at build time, or on a revalidation interval), while RSC is an orthogonal rendering model describing which components run only on the server and never ship JS to the client.
Deep Dive Analysis:
  • SSG produces the fastest TTFB by serving pre-built HTML from a CDN but can't reflect data changes without a rebuild.
  • SSR guarantees freshness on every request at the cost of higher server load and TTFB.
  • ISR statically serves a page but revalidates it in the background after a configured interval or on-demand tag/path invalidation, blending SSG's speed with near-fresh data.
  • RSC applies within any of the above: a Server Component fetches and renders on the server, while nested 'use client' components still hydrate and run in the browser.
Interviewer Takeaway: Pick SSG/ISR for content that tolerates some staleness, SSR for per-user or highly dynamic pages, and use RSC boundaries throughout to minimize client JS regardless of the strategy chosen.
Next.js App RouterMedium

Q8: What causes a hydration mismatch error, and how do you actually fix one?

Executive Answer:A hydration mismatch happens when the HTML React generates on first client render differs from the HTML the server sent, most often from non-deterministic values or browser-only APIs evaluated during render.
Deep Dive Analysis:
  • Common causes: Date.now()/Math.random() in render, checking typeof window/localStorage during render, or browser extensions injecting DOM before hydration runs.
  • The fix is to move the non-deterministic or browser-only logic into useEffect/state so it only affects the DOM after mount, rendering a stable placeholder on both server and first client pass.
  • suppressHydrationWarning should be used sparingly, only for known-safe, intentionally-different values (like a rendered timestamp), never as a blanket fix.
Interviewer Takeaway: Treat the first client render as if it were still on the server: it must produce byte-for-byte the same output as the server HTML.
State ManagementHard

Q9: Compare re-render behavior of the Context API versus an external store like Zustand or Redux.

Executive Answer:Every component consuming a Context re-renders whenever that Context's value changes, regardless of which slice it actually reads, whereas external stores let components subscribe to a narrow selector and only re-render when that specific slice changes.
Deep Dive Analysis:
  • Context has no built-in selector mechanism; a single provider value change re-renders all consumers, which is why co-locating unrelated pieces of state in one Context provider hurts performance at scale.
  • Stores like Zustand/Redux use external subscription (often via useSyncExternalStore) with selector functions, so a component reading store.user won't re-render when store.cart changes.
Interviewer Takeaway: Use Context for low-frequency, cross-cutting values; reach for a selector-based store once you have frequent updates or many independent consumers.
Hooks & LifecycleMedium

Q10: Why does a stale closure bug happen inside useEffect or useCallback, and how do you fix it?

Executive Answer:A stale closure occurs when a callback captures a variable's value from the render it was created in, and that callback is never re-created because it's missing from a dependency array, so it keeps using the outdated value.
Deep Dive Analysis:
  • Every render creates new closures over that render's props/state; omitting a value from useEffect/useCallback's dependency array pins the closure to whatever that value was on the render it was defined.
  • Fixes include adding the missing dependency, using the functional state updater form to avoid needing the value at all, or storing the latest value in a ref for effects that intentionally shouldn't re-run.
Interviewer Takeaway: A stale closure is not a hook 'bug' — it's the expected behavior of missing a dependency; the fix is always about the deps array or how the value is accessed, not the hook itself.
Performance & OptimizationMust-Know

Q11: When should you use useMemo or useCallback, and when do they actually hurt more than help?

Executive Answer:Use them when a computation is genuinely expensive or when a stable reference is required to prevent a memoized child or an effect from re-running unnecessarily; overusing them adds memory and comparison overhead without measurable benefit.
Deep Dive Analysis:
  • useMemo/useCallback have their own cost: storing the previous value/deps and doing a comparison every render, which can exceed the cost of just recomputing a cheap value.
  • They are most valuable directly upstream of a React.memo child or inside a dependency array of another hook, where referential stability actually changes behavior.
Interviewer Takeaway: Profile before memoizing; add useMemo/useCallback to fix a measured re-render or computation cost, not as a reflexive habit.
Hooks & LifecycleMedium

Q12: How would you design a custom hook that safely handles an async request with cancellation on unmount or re-invocation?

Executive Answer:Use an AbortController created inside the effect, pass its signal to fetch, and return a cleanup function that calls controller.abort(), guarding the error handler against the resulting AbortError.
Deep Dive Analysis:
  • This prevents setting state on an unmounted component and avoids race conditions where a stale, slower request resolves after a newer one and overwrites fresh data.
  • The hook should expose { data, loading, error } and re-run the effect whenever its real dependencies (like a query parameter) change.
Interviewer Takeaway: Any effect that starts an async operation should have a corresponding cleanup that cancels or ignores that operation's result.
Fiber & ReconciliationHard

Q13: Explain the difference between the render phase and commit phase, and why only the render phase is interruptible.

Executive Answer:The render phase builds the work-in-progress Fiber tree by calling components and diffing, and is pure/side-effect-free so React can safely pause or discard it; the commit phase applies DOM mutations and must run to completion synchronously so the user never sees a half-updated UI.
Deep Dive Analysis:
  • Because render can be abandoned and retried, any side effects (DOM writes, subscriptions) placed directly in a component body rather than in an effect would be unsafe to run multiple times or partially.
  • The commit phase is intentionally synchronous and uninterruptible precisely because a torn, partially-applied DOM update would be visibly broken.
Interviewer Takeaway: Keep component bodies pure (safe to call multiple times); only the commit phase and effects are allowed to touch the outside world.
Fiber & ReconciliationMust-Know

Q14: Why are array indexes bad keys for dynamic lists, and what specifically breaks?

Executive Answer:React uses keys to match Fibers across renders by identity, not position; when a list reorders, filters, or inserts items, index-based keys cause React to associate the wrong internal state (like input values or animation state) with the wrong data item.
Deep Dive Analysis:
  • If item 3 is deleted, every item after it shifts to a 'new' index, so React reuses the DOM/state of the wrong item instead of unmounting the deleted one.
  • This is especially visible with uncontrolled inputs, CSS transitions, or component-local state inside list items, which appear to 'stick' to the wrong row.
Interviewer Takeaway: Always key list items by a stable, unique identifier from the data itself (an id), never by array position.
React 19 FeaturesMedium

Q15: What is useOptimistic and when would you reach for it instead of local state?

Executive Answer:useOptimistic renders a provisional version of state that automatically reconciles with (or reverts on failure of) an in-flight async Action, replacing manual 'optimistic update + rollback on error' boilerplate.
Deep Dive Analysis:
  • It takes the current confirmed state and an update function, returning a temporary optimistic value that is shown immediately while the real mutation is pending.
  • Once the associated transition/Action completes, React automatically replaces the optimistic value with the real result (or discards it on error), without a manual try/catch rollback.
Interviewer Takeaway: Reach for useOptimistic specifically when a UI update is tied to a pending async Action (a form submit, a Server Action) rather than for plain synchronous local state changes.
Next.js App RouterHard

Q16: How does streaming SSR with Suspense boundaries work in the Next.js App Router?

Executive Answer:The server renders and streams HTML incrementally: fast, non-suspended parts of the tree are sent immediately, while sections wrapped in Suspense whose data isn't ready yet are replaced with a fallback and streamed in later once resolved.
Deep Dive Analysis:
  • This relies on HTTP chunked transfer encoding — the connection stays open and the server pushes additional HTML/script chunks that swap fallback content in place as data resolves.
  • It lets a page show its header/navigation/above-the-fold content instantly while a slow, data-heavy widget further down streams in separately, improving perceived performance without blocking the whole page.
Interviewer Takeaway: Wrap slow, independent data-fetching sections in their own Suspense boundary (often via loading.tsx per route segment) rather than one boundary around the whole page.
Performance & OptimizationMedium

Q17: What is React.memo, and why doesn't it always prevent a component from re-rendering?

Executive Answer:React.memo skips re-rendering a component if its props are shallowly equal to the previous render's props, but it does nothing to stop re-renders triggered by the component's own internal state, Context, or non-memoized prop references like inline functions/objects.
Deep Dive Analysis:
  • An inline arrow function or object literal passed as a prop creates a new reference every parent render, defeating the shallow-equality check unless the parent also memoizes it with useCallback/useMemo.
  • React.memo only affects props comparison for that one component; it has no effect on children re-rendering due to their own state or Context changes.
Interviewer Takeaway: React.memo is only effective when paired with stable prop references upstream — memoizing a component without memoizing what's passed into it is a common wasted optimization.
State ManagementMust-Know

Q18: How do you decide between local component state, lifted state, Context, and a global store?

Executive Answer:Start as local as possible and only widen scope when multiple components genuinely need to share and synchronize the same value — local state for one component, lifting for a few siblings, Context for infrequent cross-cutting values, and a store for complex or high-frequency global state.
Deep Dive Analysis:
  • Lifting state too early leads to prop drilling; introducing a global store too early adds unnecessary complexity and re-render surface area for state only two components actually need.
  • A good heuristic: if the state is derived from or mirrors server data, prefer a server-cache library (React Query/SWR) or Server Components over duplicating it in client global state.
Interviewer Takeaway: State should live at the lowest common ancestor that actually needs it — no lower, no higher.
React Server ComponentsHard

Q19: What are React Server Components, and what can and can't run inside them?

Executive Answer:Server Components render exclusively on the server (or at build time), can directly access databases, file systems, and secrets, and never ship their code to the client bundle, but they cannot use state, effects, or browser-only APIs since they never run in the browser.
Deep Dive Analysis:
  • They can be async functions that await data directly in the component body, eliminating a separate data-fetching layer for that part of the tree.
  • To add interactivity (onClick, useState, useEffect), you compose in a Client Component marked 'use client', passing it serializable props from the Server Component parent.
Interviewer Takeaway: Default to Server Components for anything that doesn't need interactivity or browser APIs, and push 'use client' boundaries as far down the tree (to the smallest interactive leaf) as possible.
Hooks & LifecycleMedium

Q20: What's the difference between useState's functional updater form and passing a direct value?

Executive Answer:The functional form, setCount(prev => prev + 1), always computes off the latest pending state, even across multiple batched updates in the same tick, while a direct value like setCount(count + 1) uses the value captured in that render's closure.
Deep Dive Analysis:
  • Calling setCount(count + 1) twice in the same handler only increments once, because both calls close over the same stale count from that render.
  • Calling setCount(prev => prev + 1) twice correctly increments twice, since each functional update receives the result of the previous one in the queue.
Interviewer Takeaway: Always use the functional updater form when the new state depends on the previous state, especially inside loops, timers, or multiple rapid dispatches.
Next.js App RouterMedium

Q21: What is the purpose of the 'use client' and 'use server' directives?

Executive Answer:'use client' marks a module boundary telling the bundler that this component (and everything it imports) must ship to and run in the browser; 'use server' marks a function as a Server Action callable from the client but executed only on the server.
Deep Dive Analysis:
  • 'use client' doesn't mean the component only renders on the client — it's still server-rendered for the initial HTML, then hydrates; it only changes where the code is included in the JS bundle.
  • 'use server' functions are the mechanism behind Server Actions, generating a secure RPC endpoint the client can invoke, without hand-writing a traditional API route.
Interviewer Takeaway: Think of these directives as bundler boundaries, not rendering-location switches — Server Components are the default; 'use client' opts into client bundling, not out of SSR.
Performance & OptimizationHard

Q22: How does React batch state updates in event handlers versus async callbacks like setTimeout or a fetch .then()?

Executive Answer:Since React 18, all state updates are automatically batched into a single re-render regardless of where they occur — inside React event handlers, promises, setTimeout, or native event listeners — unlike React 17, which only batched inside React's own synthetic event handlers.
Deep Dive Analysis:
  • Automatic batching means multiple setState calls inside a fetch .then() callback now trigger just one re-render instead of one per call, matching the behavior developers previously only got inside onClick handlers.
  • flushSync() is the escape hatch to force a synchronous, unbatched update when you specifically need the DOM updated before the next line of code runs.
Interviewer Takeaway: Don't rely on intermediate render results between sequential setState calls in the same tick; batching means only the final combined state is rendered.
Hooks & LifecycleMust-Know

Q23: What is the dependency array footgun in useEffect, and how does eslint-plugin-react-hooks help?

Executive Answer:The footgun is treating the dependency array as a 'run when this changes' trigger list you curate by intuition, rather than an exhaustive list of every reactive value the effect actually reads — the linter mechanically enforces the latter.
Deep Dive Analysis:
  • Omitting a used value to 'stop an effect from re-running too often' produces stale-closure bugs instead of solving the real problem, which is usually that the effect is doing too much or needs restructuring.
  • The lint rule statically analyzes the effect body and flags any referenced reactive value missing from the array, catching bugs before runtime.
Interviewer Takeaway: If you're tempted to omit a dependency, that's a signal to refactor (split the effect, use a ref, or move logic into an event handler) rather than to disable the lint rule.
Fiber & ReconciliationMedium

Q24: How does React's diffing algorithm decide whether to update a component in place versus unmount and remount it?

Executive Answer:React compares elements at the same position in the tree: if the element type is the same, it updates the existing Fiber and DOM node in place; if the type differs (or the key differs), it tears down the old subtree entirely and mounts a new one, discarding all state.
Deep Dive Analysis:
  • Changing a <div> to a <span> at the same tree position, or swapping which component renders at that position, forces a full unmount/remount even if the new element looks similar.
  • This is why conditionally rendering entirely different component types for the 'same' UI slot resets local state, while conditionally changing props on the same component type does not.
Interviewer Takeaway: To intentionally force a remount (resetting all internal state), change the key; to avoid an accidental remount, keep the same element type at a given tree position.
Next.js App RouterHard

Q25: Explain the caching layers in the Next.js App Router: Request Memoization, Data Cache, Full Route Cache, and Router Cache.

Executive Answer:Request Memoization dedupes identical fetch calls within a single render pass, the Data Cache persists fetch results across requests and deployments until revalidated, the Full Route Cache stores the rendered HTML/RSC output for static routes, and the Router Cache is a client-side cache of visited segments for instant back/forward navigation.
Deep Dive Analysis:
  • Request Memoization only lives for the duration of one server render, preventing the same data being fetched twice if two components in the tree request the same URL.
  • The Data Cache is what ISR's revalidate option actually controls, and can be invalidated on-demand via revalidateTag/revalidatePath.
  • The Router Cache is purely client-side and can serve stale data briefly on back navigation, which is a common source of 'why didn't my update show up' confusion.
Interviewer Takeaway: When data appears stale in the App Router, diagnose which of the four caches is responsible before reaching for a blanket 'no-store' fetch option.
State ManagementMedium

Q26: What is prop drilling, and how does component composition (passing children as props) avoid it without introducing Context?

Executive Answer:Prop drilling is passing a value through several intermediate components that don't use it themselves just to reach a deeply nested consumer; composition avoids this by having a high-level component render children/slots directly, so the value-holding parent renders the consumer as a child rather than passing data through unrelated layers.
Deep Dive Analysis:
  • Instead of <Layout data={data}><Sidebar data={data} /></Layout> threading data through Layout, you can render <Layout><Sidebar data={data} /></Layout> directly where data is available, bypassing Layout entirely.
  • This 'slots' pattern (children or named render props) is often a simpler fix than Context for one-off drilling, reserving Context for truly global, many-consumer values.
Interviewer Takeaway: Reach for composition before Context when the drilling is just a few unnecessary layers rather than a genuinely global concern.
React 19 FeaturesMust-Know

Q27: What does the React Compiler (automatic memoization) change about how developers write components?

Executive Answer:The React Compiler analyzes component code at build time and automatically inserts memoization equivalent to useMemo/useCallback/React.memo, so components can be written in plain, straightforward style without manual memoization for most performance-sensitive re-render cases.
Deep Dive Analysis:
  • It works by understanding React's rules (components/hooks are pure, props/state changes drive re-renders) to safely determine when a value or subtree can be skipped, rather than requiring the developer to manually track dependencies.
  • Manual useMemo/useCallback are still valid and sometimes still necessary (e.g. for referential stability contracts the compiler can't infer), but they become the exception rather than the default habit.
Interviewer Takeaway: Understand what the compiler automates so you can explain in an interview why 'wrap everything in useMemo' is becoming outdated advice, while still knowing the manual mechanics it replaces.
React Server ComponentsHard

Q28: How would you implement a custom hook using useSyncExternalStore to safely subscribe to an external data source?

Executive Answer:useSyncExternalStore takes a subscribe function and a getSnapshot function, letting React tear-safely read from a store outside its own state system (like a browser API, a third-party store, or WebSocket connection) even under concurrent rendering.
Deep Dive Analysis:
  • Without it, a component reading directly from a mutable external source during render risks 'tearing' — different parts of the same tree seeing different snapshots when a concurrent render is interrupted mid-way by a store update.
  • It also requires a getServerSnapshot argument to provide a consistent value during SSR, since the external store may not exist on the server (e.g. window.matchMedia).
Interviewer Takeaway: Any custom hook that subscribes to a value living outside React (browser APIs, third-party libraries) should be built on useSyncExternalStore rather than manual useEffect + useState, to stay concurrent-safe.
Common Mistakes

Mistakes That Sink Otherwise Strong Candidates

Omitting reactive values from a useEffect dependency array to 'stop an infinite loop.'

Why it happens: The effect re-runs too often, so removing the offending dependency looks like a quick fix and silences the linter warning.

The fix: Include every reactive value the effect reads; if that causes excessive re-runs, restructure the effect (split it, move logic to an event handler, or store the value in a ref) instead of hiding the dependency.

Using the array index as a list item's key.

Why it happens: It's the most convenient value available inside a .map(item, index) call and works fine until the list is reordered, filtered, or has items inserted/removed.

The fix: Key list items by a stable, unique identifier from the underlying data, never by position.

Mutating state directly, such as pushing into an array or setting a property on a state object.

Why it happens: It mirrors familiar imperative JavaScript, and the mutation can even appear to work because React sometimes still re-renders from an unrelated update.

The fix: Always create a new reference (spread syntax, array methods that return new arrays, or a library like Immer) so React's shallow-equality checks correctly detect the change.

Fetching data inside useEffect with no cancellation, leading to race conditions and 'set state on unmounted component' warnings.

Why it happens: The naive fetch-in-effect pattern is what most tutorials teach first, and cancellation is easy to forget.

The fix: Use an AbortController in the cleanup function, or move data fetching to a library (React Query/SWR) or Server Components that already handle cancellation and caching.

Storing frequently-changing global state in a single large Context provider.

Why it happens: Context feels like a lightweight built-in global store, so it's a natural first reach for shared state.

The fix: Split Context by concern and memoize its value, or move high-frequency state into a selector-based external store so unrelated consumers don't re-render on every change.

Marking whole pages 'use client' because one small widget needs interactivity.

Why it happens: It's the path of least resistance when a build error demands a client boundary somewhere in the tree.

The fix: Push 'use client' down to the smallest interactive leaf component and keep the surrounding layout and data-fetching as Server Components.

Wrapping every value and function in useMemo/useCallback as a default habit.

Why it happens: Blog posts and cargo-culted 'best practices' present memoization as always-safe performance hygiene.

The fix: Profile with React DevTools first; memoize only expensive computations or references that must stay stable for a memoized child or a dependency array.

Rendering non-deterministic values like Date.now(), Math.random(), or window/localStorage checks directly during render in an App Router page.

Why it happens: It's easy to forget the server has no window/localStorage and that the same render function runs on both server and first client pass.

The fix: Move browser-only or non-deterministic logic into useEffect so it only affects the DOM after mount, keeping the initial server and client output identical.

Cheat Sheet

Quick-Reference Cheat Sheet

Rules of Hooks
Call orderOnly call hooks at the top level — never inside loops, conditions, or nested functions
Call siteOnly call hooks from React function components or other custom hooks
NamingCustom hooks must start with 'use' so the linter and React can track them
use() exceptionuse() may be called conditionally and in loops, unlike every other built-in hook
CleanupReturn a function from useEffect to cancel subscriptions, timers, or in-flight fetches
Rendering Strategy Comparison
SSGBuilt once at deploy time, served from CDN — fastest TTFB, best for rarely-changing content
SSRRendered per request on the server — always fresh, higher TTFB and server load
ISRStatic pages revalidated on a timer or on-demand — near-fresh data at near-static speed
RSCComponents render server-only and ship zero client JS by default, streamed to the client
CSRRendered fully in the browser after JS loads — best for highly interactive, low-SEO dashboards
Next.js App Router File Conventions
page.tsxDefines the unique UI for a route segment
layout.tsxShared UI wrapping child segments; preserves state across navigation
loading.tsxAutomatic Suspense fallback shown while a segment's data loads
error.tsxClient-side error boundary scoped to a route segment
route.tsDefines a Route Handler (API endpoint) for a segment
template.tsxLike layout.tsx but remounts on every navigation, resetting state
Fiber & Render Cycle Vocabulary
Render phasePure and interruptible — builds the work-in-progress Fiber tree
Commit phaseSynchronous — mutates the real DOM and fires layout effects
ReconciliationThe diff that assigns Placement/Update/Deletion effect tags
LanesPriority buckets (Sync, Transition, etc.) letting urgent work interrupt less urgent work
Double bufferingReact keeps current and work-in-progress trees, swapping pointers on commit
State Management Decision Guide
Single-component UI stateuseState / useReducer
Shared between a few nearby componentsLift state up, or compose with children/slots
Low-frequency cross-cutting valuesContext API (theme, auth session, locale)
Complex or high-frequency global stateExternal store with selectors (Zustand, Redux Toolkit, Jotai)
Server-owned dataReact Query/SWR, or RSC + Server Actions — not client global state
Performance Optimization Toolkit
React.memoSkips a component's re-render when its props are shallowly equal
useMemoMemoizes an expensive computed value between renders
useCallbackMemoizes a function reference to preserve equality for children/effects
key propForces a remount of a subtree when identity should reset
React CompilerAutomatically inserts memoization at build time, reducing manual useMemo/useCallback
Code splittingReact.lazy + Suspense to defer loading a bundle until it's needed
Assessment Integration

Recommended Practice Quizzes on QuizCluster

Test your retention and prepare for timed live coding and MCQ technical screening rounds:

Frequently Asked Questions

Do I need to memorize React's internal source code for interviews?

No, but you do need the mental model: Fiber trees, the render vs commit phase split, and priority lanes. Interviewers use these topics to test depth beyond hook syntax, not to test whether you've read the React source line by line.

Is Redux still relevant given Context and Server Components?

Yes, but its scope has narrowed. With React Server Components and Server Actions increasingly owning server data, external stores like Redux or Zustand are now best reserved for genuinely client-only, high-frequency UI state rather than mirroring data the server already owns.

Should I still learn class component lifecycle methods for interviews in 2026?

You rarely need to write new class components, but understanding the conceptual mapping (componentDidMount/componentDidUpdate to useEffect, componentWillUnmount to a cleanup function) is still useful since interviewers use it to probe whether you understand hooks or just memorized their syntax, and legacy codebases still exist.

How much Next.js should a 'React' interview candidate actually know?

Quite a lot in practice. Because the App Router is the dominant way companies ship React today, expect SSR/SSG/ISR/RSC and hydration questions even in interviews framed as 'pure React,' since most teams can't separate the two in production.

Explore Other Preparation Guides

Software Engineering
How to Prepare for SDE Interview: Complete 2026 Roadmap
16 min readRead →
Java Ecosystem
How to Prepare for Java Developer Interview: Core to Spring Boot & JVM
18 min readRead →
Microservices & Distributed Systems
How to Prepare for Microservices Developer Interview: Distributed Architecture & Cloud
17 min readRead →
System Design
System Design Interview Guide: Complete 2026 Roadmap
21 min readRead →
Databases
SQL Interview Questions & Preparation Guide: Beginner to Advanced
17 min readRead →
Programming Languages
Python Interview Preparation: Complete Guide for 2026
17 min readRead →
Cloud & DevOps
Kubernetes Interview Guide: Architecture, Pods, Networking & Troubleshooting
17 min readRead →
Cloud & DevOps
AWS Solutions Architect Interview Guide: Real Architecture Scenarios
17 min readRead →
Databases
Database System Design: SQL vs NoSQL, Sharding, Replication & Indexing
19 min readRead →
Microservices & Distributed Systems
Kafka Interview Guide: Architecture, Consumers, Partitions & Exactly-Once Semantics
17 min readRead →
Backend Engineering
REST API Design Interview Guide: Authentication, Pagination, Versioning & Rate Limiting
15 min readRead →
Cloud & DevOps
Docker Interview Guide: Images, Containers, Networking & Production Debugging
15 min readRead →
Programming Languages
JavaScript & TypeScript Interview Guide: From Closures to the Event Loop
17 min readRead →
Backend Engineering
Node.js Backend Interview Guide: Event Loop, Streams, APIs & Scaling
17 min readRead →
Databases
Redis System Design Guide: Caching, Eviction, Persistence & Distributed Locks
17 min readRead →
Software Engineering
Concurrency Interview Guide: Threads, Locks, Race Conditions & Deadlocks
17 min readRead →
Software Engineering
Dynamic Programming Patterns: How to Recognize and Solve DP Problems
17 min readRead →