React
Component rendering, state queues, Hooks, reconciliation, commits, Effects, Suspense, and transitions.
Ask: Which component identity and render snapshot produced this UI?
From component syntax to rendering architecture
Study 111 original React interview questions across 14 connected areas. Rehearse direct answers, trace render behavior, and practice 10 production scenarios spanning performance, hydration, async races, accessibility, and frontend architecture.
Software Engineering Leader & Technical Author · Updated August 9, 2026
Version-sensitive content reviewed .
Prepare in dependency order: first understand components as pure render calculations, state as a snapshot, and identity by type, position, and key. Then study state ownership, hooks, effects as external synchronization, reconciliation, profiling, accessibility, data architecture, and testing. Senior interviews add concurrent rendering, hydration and server/client boundaries, frontend capacity, production diagnosis, and architecture that scales across teams. Strong answers trace what React does, why, the trade-off, and the user-visible consequence.
Senior candidates lose precision when they attribute closures, layout, routing, or caching to React. Debugging gets faster when the responsible layer is named before the fix.
Component rendering, state queues, Hooks, reconciliation, commits, Effects, Suspense, and transitions.
Ask: Which component identity and render snapshot produced this UI?
Closures, promises, event-loop scheduling, object identity, modules, exceptions, and CPU work.
Ask: Which lexical value, task, or promise continuation is executing?
DOM, networking, storage, accessibility tree, style, layout, paint, compositing, and input events.
Ask: Is the main thread blocked by script, layout, paint, or platform work?
Routing, loaders, caching, server rendering integration, Server Functions, deployment, and bundling policy.
Ask: Which behavior comes from the application framework rather than React itself?
Continue into the JavaScript interview hub for closures, promises, and event-loop mechanics.
Junior answers explain the component. Senior answers explain the interaction and failure. Staff answers explain the platform, ownership, capacity, migration, and organizational trade-off.
Components, props, state snapshots, JSX, event handling, lists, forms, semantic HTML, and basic Hooks.
Study this levelComposition, state ownership, reducers, refs, Effects, custom Hooks, context, testing, API integration, and rendering behavior.
Study this levelReconciliation, profiling, async races, data architecture, accessibility, hydration, failure recovery, and production diagnosis.
Study this levelFrontend platforms, system boundaries, design systems, migrations, capacity, observability, performance budgets, and organizational scale.
Study this levelLearn the stages in order. Performance answers are weak without the render model; Server Component answers are weak without clear client ownership; Staff answers are weak without product and team boundaries.
Mental model
Connect declarative UI, component calls, render snapshots, purity, one-way data flow, and the commit boundary.
Outcome: Explain what React calculates, what it mutates later, and why current render values never change in place.
Composition
Shape component boundaries, props, children, keys, controlled APIs, semantic markup, and typed interfaces.
Outcome: Design APIs that expose intent without turning every component into a configurable framework.
State
Place local, shared, server, URL, form, and cache state at the smallest correct ownership boundary.
Outcome: Prevent duplicated truth, stale derived values, and application-wide updates caused by poor placement.
Hooks
Reason about state, reducers, refs, context, memoization, custom hooks, dependencies, and closure capture.
Outcome: Use hooks as composition primitives while keeping ownership and synchronization visible.
Synchronization
Model setup, cleanup, dependencies, races, subscriptions, timers, network work, and unnecessary effects.
Outcome: Reserve effects for external synchronization and make every synchronization process independently correct.
Identity
Trace element type, tree position, keys, state preservation, parent renders, bailouts, and host mutations.
Outcome: Predict when state survives, resets, or moves—and separate rendering work from DOM work.
Evidence
Use profiles, commit data, browser traces, state locality, virtualization, splitting, and bundle evidence.
Outcome: Optimize the limiting work instead of adding memoization by reflex.
Architecture
Distinguish concurrent rendering, transitions, Suspense, SSR, hydration, streaming, and Server Components.
Outcome: Name which behavior belongs to React, which belongs to a framework, and where serialization and interaction boundaries sit.
Product quality
Design observable data flows, resilient forms, user-centered tests, keyboard behavior, focus, and error recovery.
Outcome: Treat correctness as user-observable behavior, including assistive technology and failure states.
Systems
Design autocomplete, large lists, real-time dashboards, modals, notifications, and form platforms end to end.
Outcome: Connect components to data contracts, capacity, accessibility, testing, and observability.
Staff+
Evaluate design systems, microfrontends, migrations, performance budgets, governance, and multi-team boundaries.
Outcome: Choose architecture that scales change and ownership—not merely the component tree.
Each diagram answers a different failure class: expensive work, state that moves, callbacks that see old values, and HTML that cannot hydrate. Use the model before naming an optimization.
Calculation, tree identity, host mutation, and browser paint are related—but they are not one phase.
Open render deep dive →Type, sibling position, and key connect one render's component to the next.
Open identity deep dive →A callback created in render N keeps render N's values even when it runs after render N+1.
Open closure deep dive →Server HTML is reused only when the first client-rendered tree describes the same UI.
Open hydration deep dive →State categories have different authority, lifetime, sharing, and freshness semantics. A single global-store API can hide those differences without resolving them.
Open the state taxonomy answerComponent-owned interaction such as open, hover, selection, or a small draft.
Client-authoritative behavior coordinated across a deliberate subtree or store.
A cached view of remote authority with freshness, invalidation, and failure rules.
Shareable, navigable state such as query, tab, filters, and pagination.
Editable, possibly invalid work that will be validated and submitted to an authority.
React 19.2 is the current stable feature series and React Compiler 1.0 is stable. Server Components are stable in React 19, while the bundler and framework APIs that implement them can change between minor versions. Framework-specific data loading and server functions are labelled explicitly.
Interview relevance comes from boundary and trade-off reasoning, not memorizing every new export.
Predict from state snapshots and component identity before revealing the render sequence. These are not trick questions; each exposes a production-relevant runtime rule.
prediction.tsx
const [count, setCount] = useState(0)
function handleClick() {
setCount(count + 1)
setCount(count + 1)
setCount(count + 1)
}After one click, what count appears?
Choose an outcome to reveal the render sequence and production consequence.
Change one architectural condition and trace what renders, what can bail out, what commits, and whether row state survives. This is a teaching model—not a Fiber simulator.
Query state updates in App.
No host node of its own
No DOM change
Input/results state changes
Visible result set may change
Identity and local state preserved
No DOM change
Read it: Lifting query state to App expands the default render path. Results still needs the new query, so memo cannot skip it.
The correction matters more than the slogan. Each misconception collapses two different mechanisms into one convenient rule.
React adds calculation so it can manage declarative updates predictably. Performance depends on the work, host mutations, DOM size, and browser behavior.
It caches one calculation when dependencies repeat. It also compares dependencies, retains values, and can add stale-dependency risk.
Context distributes a value. State libraries may add ownership, selectors, middleware, persistence, and debugging semantics.
An Effect models one synchronization process with an external system; arbitrary mount/update logic is usually the wrong abstraction.
Function creation is normally cheap. Identity matters only when a measured consumer or dependency relies on it.
Own state and consumed Context still update a memoized component, and memo is an optimization rather than a guarantee.
Keys define sibling identity and directly control state preservation, cleanup, and remount behavior.
It queues work for a future render; the current render and its closures keep their snapshot.
Server Components remove noninteractive code from the client graph; interactive state, Effects, and browser APIs still need Client Components.
Start with evidence, identify the responsible layer, mitigate user harm, change one hypothesis, and verify the same workload. Hook vocabulary alone does not diagnose an incident.
Each scenario expands into a concise answer in the question library; the cornerstone incidents continue into investigation guides.
Scenario 01
Scenario 02
Scenario 03
Scenario 04
Scenario 05
Scenario 06
Scenario 07
Scenario 08
Scenario 09
Scenario 10
The unit of design is no longer one component. It is a platform boundary, migration path, capacity contract, contribution model, and reversible technical strategy.
Tokens, accessible primitives, versioning, codemods, adoption incentives, governance, and measurable product impact.
Open architecture questionOrganizational autonomy versus runtime integration, dependency, performance, UX, and observability cost.
Open architecture questionIngress, coalescing, snapshot cadence, selector subscriptions, overload policy, freshness SLOs, and resync.
Open architecture questionSchema evolution, accessible rendering, validation, migrations, extensions, localization, and operational ownership.
Open architecture questionThe React hub owns the interview mental model. Existing implementation guides and JavaScript mechanics remain the canonical deeper resources.
Reuse the existing debounce, AbortController, cache, and stale-response mechanics instead of duplicating them here.
Study sentinel loading, in-flight guards, end-of-data behavior, retry, and the virtualization boundary.
Use the dedicated production guide for dates, storage, invalid HTML, CDN mutations, and deployment-only mismatches.
Explore a framework-free tree diff without confusing the exercise with React's current internal implementation.
Browse the React 19, hooks, debounce, accordion, and TypeScript component articles already maintained in the repository.
Connect frontend state and capacity decisions to APIs, reliability, data ownership, and distributed systems.
Search questions, direct answers, tags, or runtime concepts. Reveal concise answers in place, then open cornerstone rendering and production topics as layered deep dives.
Showing 111 of 111 questions
Declarative UI, component execution, render snapshots, purity, one-way data flow, and render-versus-commit reasoning.
Interview focus: Predict behavior from a render snapshot before reaching for hook or DOM terminology.
Render cycle
Build the core model: React calls components to calculate a tree description, then commits only necessary host changes.
A render is React calling components to calculate the next UI description from props, state, and context. That calculation must stay pure and may be restarted. React then compares the next tree with the previous one, and during commit applies necessary host changes such as DOM mutations and refs. Browser layout and paint happen after React's work; component execution does not imply a DOM update.
State snapshots
Explain why setting state schedules another render rather than mutating the variable already captured by the current render.
Each render receives fixed state values. Calling a setter queues work for a future render; it does not change the state variable inside the current event handler or closure. That is why several setCount(count + 1) calls from one render all calculate from the same count. Functional updates express a transition from the queued previous value when the next state depends on it.
Purity
Connect pure component calculations to retries, interruption, Strict Mode checks, and predictable composition.
React needs to call rendering code whenever it must calculate UI, and modern rendering work may be repeated, paused, or abandoned before commit. A pure component returns the same JSX for the same inputs and does not mutate external state during render, so those behaviors remain safe. User-caused side effects belong in event handlers; render-caused synchronization with external systems belongs in Effects.
Render cycle
Separate calculating the next tree from applying observable host changes.
Render is the pure calculation phase: React calls components and determines the next tree. Commit is when React applies the resulting host changes, updates refs, and runs the appropriate effect lifecycle. A component can render without producing a DOM mutation, and work calculated during concurrent rendering can be discarded before commit. Browser layout and paint are separate stages after DOM changes.
Render triggers
Identify initial rendering, local state, ancestor rendering, context changes, and subscribed external-store updates.
A component renders initially when its tree is mounted. Later it can render because its own state changed, an ancestor rendered it again, a consumed context value changed, or a subscribed external store notified it. A render does not mean the DOM changed. Memoization can skip some ancestor-driven renders, but a component's own state and consumed context still update it.
Render propagation
Explain the default recursive render path and the conditions under which React can reuse prior work.
By default, when a component renders, React evaluates the child elements it returns and renders that subtree. React can reuse work at explicit or compiler-created memo boundaries when relevant inputs are unchanged, and already-created children passed through composition may keep stable element identity. Treat this as performance behavior, not correctness: children must be pure and correct whenever React calls them.
Identity
Connect state preservation to element type, position in the tree, and keys.
React associates state with a component identity in the rendered tree, determined primarily by element type, position among siblings, and key. If that identity remains stable, React preserves state across renders. If the type or key changes, or the component moves to a different identity position, React treats it as a different instance, cleans up the old one, and initializes new state.
React values
Distinguish the component code React calls from the immutable description that JSX creates.
A component is a function or class React can call to calculate UI. A React element is the immutable description produced by JSX or createElement: it records a type, props, and key. Rendering a component returns elements; React uses those elements to construct and reconcile the tree. Elements are descriptions, not DOM nodes and not mounted component instances.
JSX, elements, props, children, composition, controlled APIs, conditional rendering, lists, and TypeScript contracts.
Interview focus: Design a small, semantic API with explicit ownership and accessible defaults.
JSX
Explain JSX as syntax transformed into calls that create React element descriptions.
JSX is syntax for expressing a React element tree alongside JavaScript. A build transform converts it to calls in the JSX runtime that create element descriptions containing the type, props, and key. JSX is neither HTML nor a template string: JavaScript expressions run while the component renders, and React later interprets the resulting elements for the target renderer.
Component inputs
Frame props and state around who owns a value rather than whether one can change.
Props are inputs owned by the parent for that render; the child must treat them as read-only. State is memory owned by a component identity and updated through React. Both are snapshots and both can differ on the next render. The design question is ownership: keep one source of truth at the lowest boundary that coordinates every reader and writer.
Composition
Evaluate flexible child composition against a prop matrix that creates invalid combinations.
Composition lets callers supply meaningful UI pieces through children or named slots while the parent owns layout and behavior. A large boolean-and-variant prop matrix creates combinatorial states, unclear precedence, and a brittle abstraction. Good APIs keep invariants inside the component, expose a small stable contract, and use composition only where callers genuinely need structural freedom.
Component APIs
Compare parent-owned value/onChange contracts with component- or DOM-owned state.
A controlled component receives its current value and reports requested changes, so the parent owns the source of truth. An uncontrolled component owns its state internally, often initialized with a default value, and may expose a ref or final value. Controlled APIs coordinate validation and cross-field behavior; uncontrolled APIs reduce update coupling. A reusable component should not silently switch modes during its lifetime.
Conditional rendering
Reason about the tree React sees rather than the visual branch labels in source code.
React preserves state when the same component type occupies the same identity position, even if different source-code branches produced it. It resets state when the type or key at that position changes. To predict behavior, sketch the resulting element tree for each condition. Avoid defining component functions inside another component because that creates a new component type every render.
TypeScript
Use types to encode valid component modes rather than documenting illegal prop combinations.
Type the smallest public contract and encode mutually exclusive modes with discriminated unions instead of optional-prop combinations. Use explicit event and ref element types, model children according to the actual composition contract, and make callback payloads domain-specific. React.FC is optional; plain functions often preserve clearer generics and do not imply a children prop you may not support.
State snapshots, queued updates, batching, functional updates, reducers, derived state, lifting, and state placement.
Interview focus: Identify the source of truth, event boundary, and exact transition before choosing storage.
State ownership
Place state at the lowest owner that must coordinate all readers and writers.
Keep state local when only one subtree needs it; locality reduces coupling and render reach. Lift it to the nearest common owner when multiple peers must coordinate one source of truth. Do not lift state preemptively into a page or global store. Ask who reads it, who writes it, whether it must survive navigation, and whether a URL, server cache, or form boundary owns it better.
State structure
Compute values from existing inputs during render unless independent historical state is required.
Derived data can be calculated from current props or state, such as a filtered list or full name. Storing another copy creates two sources of truth and a synchronization problem—often an unnecessary Effect and extra render. Calculate cheap derived values during render; memoize only measured expensive work. Store a value separately only when it represents independent user intent or history.
State structure
Choose a state shape that represents valid domain states and supports atomic transitions.
Group values that always change together and separate values with independent lifetimes. Avoid contradictory booleans such as isLoading, isSuccess, and isError when a discriminated status can represent one valid state. Normalize large relational collections when updates otherwise require duplicating entities. A reducer helps when named events must preserve several invariants atomically.
State updates
Explain why multiple updates can produce one render and why value-form updates still read one snapshot.
React queues state updates and normally processes them together before the next render, avoiding an intermediate render after every setter call. Batching changes render frequency, not snapshot semantics: three setCount(count + 1) calls from one handler still enqueue the same replacement value. Three functional updates, setCount(c => c + 1), compose against the queued value and produce three increments.
State updates
Use an updater when the next value depends on the prior queued value.
Use setState(previous => next) when the next value depends on the previous value, especially for multiple updates in one event or callbacks that run later. React applies updater functions in queue order to the pending state. If the next value comes entirely from current event data or props, a direct replacement is often clearer.
State architecture
Separate state by authority, persistence, sharing, and freshness instead of forcing everything into one store.
Local UI state belongs to a component interaction; shared client state coordinates client-owned behavior; server state is a cached view of remote authority with freshness rules; URL state is navigable and shareable; form state is an editable draft; cache state has eviction and invalidation semantics. Choosing one global store for all six hides different lifetimes and correctness rules behind one API.
Event boundaries
Place user-caused work in the interaction and render-caused external synchronization in an Effect.
If work happens because the user performed a specific action—submit, purchase, send—run it in that event path. If a component being present or a reactive value changing requires synchronization with an external system, use an Effect. Moving event-specific work into an Effect loses the cause, can repeat on remount, and creates indirect state choreography.
useState, useReducer, useRef, useContext, useMemo, useCallback, custom hooks, and stale closures.
Interview focus: Explain lifetime, closure capture, identity, and the architectural cost of each hook—not its syntax.
Hook rules
Keep hook calls in a stable structural order so React can associate hook state with the correct call site.
React associates Hook state with the component and the order of Hook calls. Conditional, looped, or nested function calls can change that order between renders and attach state to the wrong call. Call Hooks unconditionally at the component or custom-Hook top level, and express conditional behavior inside the Hook or by splitting components. The official lint rules enforce more than naming style—they protect runtime correctness.
useReducer
Prefer a reducer when named events coordinate multiple fields or make state transitions easier to test.
useState is simplest for independent values and direct replacements. useReducer helps when several fields change together, transitions have domain names, or invariants deserve one pure transition function. It centralizes update logic but does not make state global or automatically faster. If the reducer becomes a switch over trivial setters, it adds ceremony without a clearer model.
useRef
Use refs for mutable values whose changes should not drive rendering, or for imperative host access.
A ref is a stable mutable cell across renders. Updating ref.current does not schedule a render, so refs fit DOM nodes, timer IDs, external instances, and bookkeeping not used to calculate visible UI. State belongs to values whose changes must appear in rendering. Reading or writing refs during render generally breaks purity; use them from events, effects, or ref callbacks.
useRef
Clarify when the ref is updated and which render can observe the prior committed value.
A common pattern reads a ref during render and updates it in an Effect after commit, so the next render sees the last committed value. That timing is the behavior—not a special previous-value feature. If previous state is needed to calculate the next state, use a reducer or functional update instead. Avoid ref-based shadow state that bypasses React's rendering model.
Context
Explain why changing a provider value updates consuming components and why one broad value expands render reach.
When a provider's value changes by Object.is comparison, React updates consumers that read that context, regardless of an ancestor memo boundary. A newly created object value changes every provider render. Stabilizing identity can help, but the architectural fix is often splitting contexts by change frequency, narrowing provider placement, or moving rapidly changing data to a selector-capable external store.
Memoization
Require evidence that a calculation is expensive and dependency reuse is frequent enough to repay memoization.
Not necessarily. useMemo caches a calculation result between renders when dependencies are unchanged; it does not stop the component from rendering. Measure the calculation and render path first. Memoization adds dependency comparisons, retained memory, and correctness risk from stale dependencies. With React Compiler enabled, automatic memoization further reduces the need for manual useMemo, which remains an optimization rather than semantic storage.
Context: React Compiler 1.0 can automatically memoize calculations; manual useMemo remains available for precise control and non-compiler builds.
Memoization
Use stable function identity only when a measured consumer benefits or dependency semantics require it.
useCallback caches a function identity, not its result and not the work inside it. It can help a memoized child avoid a prop change or stabilize a dependency, but wrapping every handler adds code and dependency risk without benefit when consumers render anyway. Prefer simpler code, profile the path, and account for React Compiler automatic memoization where enabled.
Custom hooks
Extract a coherent stateful capability with an honest contract, not merely repeated lines of code.
A good custom Hook owns one coherent stateful or synchronization concern, exposes a small domain contract, and leaves UI composition with components. It makes lifecycle, dependencies, and error behavior explicit. A Hook that mixes fetching, analytics, navigation, and form state hides ownership and becomes difficult to test. Reuse is a consequence; clarity of the boundary is the primary reason.
Closures
Trace a callback back to the render whose values it captured.
Every render creates functions that close over that render's props and state snapshot. If a timer, listener, promise callback, or memoized function runs later, it still sees those captured values unless it is recreated with correct dependencies or reads the latest value through an intentional pattern. Fix the ownership or dependency model; do not suppress the linter and hope the closure stays current.
External synchronization, dependency correctness, cleanup, subscriptions, timers, request races, and effect removal.
Interview focus: Name the external system and independently prove setup, resynchronization, and cleanup.
Effect model
Define Effects as render-driven synchronization with systems outside React, not lifecycle callbacks for arbitrary code.
An Effect describes how a committed render synchronizes with an external system such as a subscription, browser API, timer, network connection, or third-party widget. React runs setup after commit, runs cleanup before resynchronizing with changed dependencies, and cleans up on unmount. If there is no external system, the code probably belongs in render or an event handler.
Avoiding Effects
Show how an Effect creates a stale intermediate render and duplicated state for a pure calculation.
fullName is fully determined by current props, so calculate it during render. Storing it creates another source of truth: React first commits stale fullName, the Effect sets it, and React renders again. The dependency array only maintains the synchronization problem. Reserve state for independent information and Effects for external systems.
Dependencies
Treat dependencies as a description of values the synchronization process reads, not a scheduling preference.
Dependencies are every reactive value read by the Effect's setup. React compares them with Object.is to decide when that external synchronization must be torn down and recreated. You do not choose dependencies to control timing; change the code so the desired dependency model is truthful—move event work to events, derive values during render, split independent Effects, or stabilize genuine inputs.
Cleanup
Model cleanup as undoing one setup before a new synchronization starts or the component leaves.
Cleanup runs before React applies the Effect setup again with changed dependencies and when the component unmounts. In development Strict Mode, React also performs an extra setup-cleanup-setup cycle to expose missing teardown. Cleanup should symmetrically disconnect, unsubscribe, clear, or cancel what that setup created; it should not perform unrelated global reset work.
Strict Mode
Explain the development-only setup/cleanup stress test and the bug it is designed to reveal.
In development, Strict Mode intentionally runs an extra Effect setup and cleanup cycle to reveal code that cannot safely disconnect and reconnect. Production does not keep that extra cycle. The fix is not a ref that suppresses setup; make cleanup undo setup and make the external operation safe for remounting. Duplicate irreversible user actions usually belonged in an event handler.
Context: This is a development-only Strict Mode behavior; production setup follows the committed lifecycle.
Async effects
Handle Alice-then-Bob request ordering with cancellation and a stale-result guard, or move fetching to a data layer.
Effects can start request A, then request B after input changes; if A finishes last, it can overwrite B's newer result. Cleanup should abort obsolete work where supported, and the completion path must ignore results that no longer match the active request. In production, prefer a framework loader or query/cache layer that also handles deduplication, freshness, retries, and navigation races.
Effect architecture
Replace effect-driven state choreography with direct calculations or one explicit event transition.
A chain where Effect A sets state for Effect B creates indirect control flow, extra commits, fragile dependencies, and intermediate states the user can observe. If the values are derivable, calculate them in render. If one user or server event causes a multi-field transition, update it together—often through a reducer. Keep separate Effects only for genuinely independent external synchronization processes.
Tree identity, keys, state preservation, parent renders, memo boundaries, reconciliation, commits, and DOM updates.
Interview focus: Separate component execution from host mutation and reason about identity by type, position, and key.
Reconciliation
Explain how React relates two render results to preserve identity and compute necessary host work.
Reconciliation relates the previous and next React trees so React can decide which component identities and host nodes to reuse, update, insert, or remove. Type, position, and keys guide that relationship. It is broader than a DOM diff: reconciliation also determines component lifetime and state preservation. The commit phase later applies the resulting host changes.
Keys
Treat a key as part of sibling identity, not as a warning-suppression attribute.
Keys tell React which sibling in the next list corresponds to which sibling in the previous list. A stable domain key lets state and DOM association follow an item through insertion, removal, and reordering. Keys need only be unique among siblings, but they must be stable across renders. Generating a new key during render makes every item appear new and resets state.
Keys
Show how position-based identity attaches state to the wrong logical item when a list changes order.
An index identifies a position, not the domain item occupying it. If items are inserted, removed, filtered, or reordered, React can reuse a component and its local state for a different item, causing wrong input values, focus, or animation state. An index is acceptable for a truly static list with no reordering and no item identity; a stable data ID is the normal choice.
Keys
Explain deliberate and accidental remounting through a changed identity.
A changed key gives the element a new identity. React unmounts the previous component, runs its cleanup, discards its state, mounts a new instance, and creates the necessary host work. This can deliberately reset a form for a new entity, but using unstable keys causes lost input, repeated subscriptions, broken focus, and unnecessary DOM work.
State preservation
State belongs to React's identity position rather than to a JSX tag in source code.
State is stored by React for an identity position in the rendered tree. Moving the same-looking JSX to a different parent or sibling identity can remove the old component and create a new one, so state resets. If state must follow a domain entity, preserve its stable keyed position or move the state to an owner whose lifetime matches the entity.
Commit behavior
A component tree can be recalculated while React reuses every existing DOM node.
Reconciliation is React's process for relating previous and next element trees, including component identity and state lifetime. DOM updating is only the host mutation work selected from that process and applied during commit. A parent and many children may render, yet if their host output is unchanged React may perform no DOM mutations. The browser separately handles style, layout, paint, and compositing.
Component identity
A new component function object on each parent render creates a different element type.
Defining a component inside another component creates a new function object each parent render. React sees a different element type at that position, so it remounts the subtree, resets state, and repeats setup and cleanup. Define component types at module scope and pass changing data through props. An inline callback or render function is different—it is a value, not necessarily an element type.
Memoization
Memo can skip an ancestor-driven render with equal props, but it is neither a semantic guarantee nor a universal shield.
React.memo can reuse a component's previous result when its parent renders and props compare equal by Object.is. The component still renders for its own state updates and consumed context changes, and React may render it for other reasons because memoization is an optimization. New object or function props defeat shallow equality. React Compiler can supply equivalent optimization automatically when enabled.
Profiler evidence, render cost, memoization, context propagation, virtualization, bundles, long tasks, and browser work.
Interview focus: Observe, profile, locate the limiting work, change one hypothesis, and verify the user-facing result.
Performance model
Distinguish cheap component calculations from expensive work inside them and from host/browser costs.
A re-render is work, but it is not automatically a performance problem. Many component calculations are cheap and produce no DOM changes. Cost comes from tree size, expensive calculations, unstable dependencies, broad context or state updates, host mutations, layout, paint, and synchronous third-party work. Measure interaction latency and profiles before trading simplicity for memoization.
Profiling
Start from a repeatable user symptom, correlate React and browser traces, then verify one targeted change.
Reproduce the exact interaction with production-like data and record a browser Performance trace plus React DevTools profile. Determine whether time is in JavaScript, React rendering, a commit, layout/paint, network, or third-party work. Identify why the expensive subtree rendered, form one hypothesis, make the smallest architectural or algorithmic change, and remeasure the user-visible latency and regressions.
Profiling
Use commit timing and render reasons to find unexpectedly broad or expensive component work.
Inspect which commits correspond to the slow interaction, their duration, which components rendered, and why inputs changed. Look for a broad subtree updated by high state or context, an individually expensive calculation, repeated commits caused by Effects, or unstable props defeating a bailout. Correlate with the browser trace because React profiles do not explain network waits, layout, paint, or third-party scripts alone.
State locality
State placed high in the tree expands the default render path even when only a leaf needs the value.
An update renders the owning component and, by default, its subtree. Moving ephemeral state closer to the components that need it reduces render reach and coupling without memoization. Before optimizing props, ask whether hover, draft input, dialog state, or selection was lifted too high. Shared state should still live at the lowest boundary that genuinely coordinates all consumers.
Context performance
Measure consumers, update frequency, and provider identity before splitting or replacing the architecture.
Profile which consumers update and inspect whether the provider recreates a composite object on unrelated changes. Stabilize genuine provider values, split contexts by responsibility and change frequency, narrow provider placement, and separate state from dispatch where useful. For high-frequency or large shared data, a selector-based external store can notify only affected readers. Do not memo every consumer before fixing the ownership boundary.
Virtualization
Render a bounded window of visible rows rather than optimizing 100,000 mounted components.
Use windowing: render only visible rows plus a small overscan buffer, represent total height with spacers, and recycle positions as the viewport moves. Define stable item identity, row measurement for variable heights, keyboard and screen-reader behavior, focus retention, and scroll restoration. Pagination bounds data transfer; virtualization bounds DOM and render work. React.memo alone leaves the huge DOM mounted.
Bundles
Split around meaningful user journeys, then prevent the new boundaries from creating request waterfalls.
Split code at route or feature boundaries that materially reduce initial JavaScript, not every small component. Provide a stable loading experience, preload likely next paths, and track chunk size, cacheability, and failed dynamic imports. Excessive splitting adds request and Suspense coordination overhead. Measure parse, compile, execution, and interaction readiness—not only compressed bundle bytes.
Browser performance
Use React profiles for component work and browser traces for scripting, style, layout, paint, and compositing.
React DevTools shows component render and commit work. The browser Performance panel shows the full main-thread task, including JavaScript outside React, style recalculation, forced layout, paint, and compositing. If a small commit triggers expensive layout on a massive DOM, memoization misses the bottleneck. Correlate timestamps and test whether the limit is component work, DOM size, CSS/layout, or network.
React 19.2, transitions, Suspense, Compiler, SSR, hydration, streaming, Server Components, and framework boundaries.
Interview focus: Distinguish stable React APIs from framework integration and unstable implementation interfaces.
Concurrent rendering
Describe interruptible and prioritized rendering without claiming components execute in parallel threads.
Concurrent rendering lets React prepare multiple versions of UI with priority-aware, interruptible render work. A lower-priority render can pause or restart so an urgent input update stays responsive; only a completed consistent tree commits. It does not mean component functions run simultaneously on multiple JavaScript threads. Purity is what makes restartable calculation safe.
Transitions
Mark a non-urgent state transition so urgent input can update while React prepares expensive UI.
useTransition marks selected state updates as non-blocking. React can interrupt that render to process urgent work such as typing, while isPending supports intentional pending feedback. It does not make expensive JavaScript faster and should not control a text input's own value. Use it when rendering the destination is costly and keeping the current interface responsive is better than blocking.
Transitions
Defer rendering a value without introducing a fixed timer or automatically reducing network traffic.
useDeferredValue lets an urgent render use the old value while React prepares a lower-priority render with the new one. It has no fixed delay and does not prevent requests from firing. Debouncing waits for input to settle before invoking work and can reduce calls. Choose based on whether the bottleneck is render responsiveness, request volume, or both.
Suspense
Coordinate loading and reveal behavior for compatible resources without treating Suspense as a generic fetching client.
A Suspense boundary defines where React may show fallback UI while a compatible child is not ready, and coordinates reveal during client rendering or streaming SSR. It does not fetch data, choose cache semantics, or make an arbitrary useEffect request suspend. The framework or data source must integrate with Suspense. Place boundaries around coherent user experiences to avoid flashing the whole page.
React Compiler
Treat the compiler as stable build-time optimization, while preserving profiling, correctness, and measured rollout.
React Compiler 1.0 is a stable build-time optimizer that analyzes component and Hook code and applies granular memoization automatically. It reduces routine useMemo, useCallback, and React.memo work, but it does not fix expensive algorithms, huge DOMs, network waterfalls, or broken state ownership. Adopt it incrementally, respect the Rules of React, test behavior, and verify performance rather than assuming every component improves.
Context: React Compiler 1.0 became stable in October 2025 and supports incremental adoption; integrations remain build-tool specific.
Server Components
Separate React's stable Server Component model from framework implementations and interactive client code.
Server Components render ahead of the client bundle in a separate environment, can access server-side data, and send rendered output rather than their component code to the browser. Interactive state, Effects, and browser APIs belong in Client Components. Values crossing the boundary must be serializable by the integration. React 19 stabilizes the component model, while bundler/framework implementation APIs can still change between minors.
Context: Server Components are stable in React 19; production routing, caching, server functions, and bundling are supplied by a framework, whose underlying RSC APIs require careful version alignment.
Server Components
Evaluate bundle removal and data locality against interaction, serialization, caching, and network boundaries.
A server boundary helps when rendering needs server-only data or heavy noninteractive libraries, and the output can cross as serializable UI without shipping that code to the client. It is less useful for highly interactive, offline, or rapidly client-updated behavior. Measure client JavaScript, waterfall removal, cache behavior, navigation latency, and server cost. A Server Component is not automatically a security boundary for data passed to clients.
Server rendering
SSR produces initial HTML; Server Components change which component code and data work belong in the client graph.
SSR renders a React tree to HTML for the initial response; interactive client components still load JavaScript and hydrate. Server Components define components that do not enter the client bundle and whose rendered result can compose with Client Components. Frameworks often combine RSC, SSR, streaming, and routing, but they solve different problems: initial HTML delivery versus the server/client component graph.
Hydration
Hydration attaches React to server-rendered HTML and requires the first client output to match.
Hydration lets React attach behavior to server-rendered HTML while reusing that DOM. The first browser render must match the server output in structure, text, attributes, and stable IDs. Dates, locale, random values, browser-only storage, invalid HTML, request-specific data, extensions, and CDN rewrites can diverge. Find and remove the nondeterministic source; suppression is only a narrow escape hatch.
Server rendering
Send an initial shell and reveal completed Suspense regions without waiting for the slowest data dependency.
Streaming SSR can send the ready application shell first, then stream later Suspense boundary output as dependencies resolve. This improves progressive delivery but introduces boundary placement, fallback stability, error recovery, caching, abort, and hydration coordination decisions. It does not remove server latency or client JavaScript cost. Frameworks determine the data, routing, and deployment integration around React's server APIs.
Effect Events
Extract non-reactive event-like logic from an Effect while still reading the latest props and state.
useEffectEvent lets an Effect call non-reactive logic that always sees current props and state without making those values dependencies of the synchronization process. It is useful when connection setup depends on roomId but a notification should use the latest theme. It is not a general dependency escape hatch, cannot be passed around arbitrarily, and does not replace normal user event handlers.
Context: useEffectEvent became stable in React 19.2.
Fetching placement, caches, stale data, optimistic work, controlled/uncontrolled inputs, validation, and large forms.
Interview focus: Design ownership, freshness, cancellation, failure recovery, and field-level update boundaries together.
Data fetching
Prefer server/framework loaders or a query cache when fetching is part of route data rather than ad hoc synchronization.
Fetching in an Effect starts after render, repeats boilerplate for loading and races, and can create parent-child waterfalls. Route data often belongs in a framework loader or server boundary; shared client data often belongs in a query/cache layer with deduplication, freshness, cancellation, and invalidation. An Effect remains reasonable for small client-only synchronization where the component truly owns the request lifetime.
Data architecture
Server state is a cached remote view with freshness and ownership constraints, not simply another global object.
Client state is authoritative inside the client for interactions such as a draft or open panel. Server state is a local snapshot of remote authority: it can become stale, be refetched, invalidated, shared, or conflict with mutations from elsewhere. A query cache models those semantics better than manually copying responses into a generic global store. Optimistic UI is a temporary client projection, not a transfer of authority.
Mutations
Update perceived state immediately while preserving pending status, rollback/reconciliation, and duplicate-submission safety.
Apply an optimistic projection only when success is likely and the rollback or correction is understandable. Give the mutation a stable identity, show pending status, prevent accidental duplicates, and reconcile with the authoritative response. On failure, restore or mark the item and offer recovery. For conflicting collaborative data, define merge/version rules instead of assuming last response wins.
Forms
Choose based on who needs each keystroke and how broadly the draft must coordinate.
A controlled input mirrors its value in React state and is useful when rendering, validation, or other fields depend on each change. An uncontrolled input lets the DOM hold the live value and reads it at a boundary such as submit, reducing render coupling. Both can be accessible and validated. Large forms often combine uncontrolled field storage or field subscriptions with React-owned form-level status.
Large forms
Bound keystroke work with field-level ownership or subscriptions before adding memoization.
Profile one keystroke, then stop routing every field change through one form component. Give fields local or uncontrolled draft storage, subscribe only affected fields and summaries, and run expensive validation at deliberate boundaries or in incremental slices. Preserve accessible labels, error association, focus-to-error, server validation, and draft recovery. Virtualize only if hidden field DOM itself is large and navigation semantics remain intact.
Caching
Define keys, freshness, invalidation, retry safety, ownership, and capacity as part of the data contract.
A cache needs stable query identity, freshness windows, deduplication, cancellation, invalidation after mutation, garbage collection, persistence rules, and authorization-aware scoping. Retries need bounded backoff and method safety; they must not amplify a failing dependency. Decide whether stale data can remain visible, how offline or partial failure appears, and how telemetry distinguishes cache hit, revalidation, and network failure.
Behavior-first tests, async assertions, mocking boundaries, keyboard interaction, focus, semantics, ARIA, and live regions.
Interview focus: Protect user-observable behavior across input modes rather than component implementation details.
Component testing
Test behavior a user or consuming component can observe, not internal state or hook calls.
A component test should exercise the public behavior: render accessible output, perform realistic user interactions, and assert the resulting UI, callback, or navigation. Query by role and label where possible because that resembles how users and assistive technology find controls. Avoid asserting private state, exact component trees, or how many times a Hook ran unless that is itself an external contract.
Testing strategy
Place tests at the cheapest boundary that proves the risk, with integration tests carrying most user-flow confidence.
Unit-test pure domain transitions and utilities; component/integration tests cover rendered behavior across components, state, and mocked network boundaries; E2E tests protect a small set of critical journeys in the deployed stack. The goal is risk coverage, not a fixed ratio. Too many isolated component mocks miss integration failures, while too many broad E2E tests become slow and hard to diagnose.
Test doubles
Mock slow or nondeterministic external boundaries while keeping meaningful application collaboration real.
Mock the network, clock, browser capability, or external service boundary when control and determinism matter. Prefer request-level mocks over mocking the data Hook implementation so caching, loading, and error UI still run. Do not mock every child component—the test then verifies wiring rather than behavior. Keep mock responses contract-shaped and include latency, empty, error, and cancellation cases.
Async testing
Wait for observable outcomes and control external time or I/O rather than inserting arbitrary sleeps.
Trigger the user action, await the resulting accessible UI state, and let the test utility flush React work through its supported APIs. Control network responses and timers explicitly. Avoid fixed sleeps, un-awaited user events, shared mutable fixtures, and assertions that race intermediate loading states. A flaky test often reveals an application race or missing ownership boundary, so diagnose it rather than only increasing timeout.
Accessibility
Start with native semantic elements, correct names, keyboard behavior, and visible focus before adding ARIA.
Choose the native element that already carries the required semantics and keyboard behavior: button, link, input, heading, list. Give controls programmatic labels, preserve visible focus, expose validation and status, and test with keyboard and a screen reader. ARIA can describe a custom widget but does not add behavior; a div with role=button still requires keyboard activation and focus management.
Focus management
Combine dialog semantics with focus entry, containment, dismissal, restoration, and background isolation.
Use a real dialog pattern with an accessible name. On open, move focus to a meaningful element; keep Tab navigation inside while modal; support Escape and an explicit close control; prevent background interaction; then restore focus to the trigger when safe. Handle nested portals, scroll lock, destructive confirmation, and route changes. Prefer a proven accessible primitive because focus edge cases are extensive.
Composite widgets
Design input, popup, keyboard navigation, result status, selection, and async behavior as one interaction contract.
Use the ARIA combobox pattern with a labelled input, controlled popup state, listbox relationship, active option indication, and documented Arrow, Enter, Escape, and Tab behavior. Keep DOM focus predictable, announce result count/loading changes without noise, preserve typed text, and expose errors. Add debounce, cancellation, stale-result guards, caching, and mobile/touch behavior without breaking keyboard semantics.
Headless and compound components, custom hooks, context boundaries, external state, domain APIs, and dependency inversion.
Interview focus: Keep change local, make invalid states difficult, and avoid abstractions that hide ownership.
State architecture
Context distributes a value through a tree; a state library may add ownership, selectors, tooling, and update semantics.
Context solves value distribution without prop drilling; it does not define state transitions, caching, selectors, persistence, or server synchronization. A reducer plus Context can be enough for modest low-frequency client state. An external store becomes useful when independent subscriptions, selector-based updates, middleware, time-travel/debugging, cross-root ownership, or established team conventions justify the added dependency.
Context architecture
A single provider couples unrelated lifetimes, update frequencies, tests, and failure domains.
Any changed composite value can update every consumer, and unrelated responsibilities become one dependency and test fixture. Split contexts by cohesive ownership and change rate, narrow provider placement, keep auth authority separate from presentation preferences, and move high-frequency dashboard data to a selector-capable store. The goal is clearer boundaries first and reduced render reach second.
External state
Use Redux when complex shared client state benefits from explicit transitions, selectors, middleware, and debugging—not by default.
Redux fits substantial shared client state with many writers, complex transitions, normalized entities, cross-feature coordination, or strong debugging and middleware needs. Redux Toolkit removes much boilerplate. It should not absorb every local input or replace a server-data cache. Compare it with Context plus reducer, an external store, URL state, and the team's operational needs before adding global ownership.
Component APIs
Expose semantic subcomponents that coordinate through a private owner without a giant configuration object.
Compound components work when a control has recognizable parts—Tabs, List, Trigger, Panel—that need shared state but flexible composition. A private context can coordinate IDs, selection, and keyboard behavior while the public API remains declarative. Keep allowed structure and accessibility invariants clear. For a simple component, named props or children are often easier than an entire compound vocabulary.
Component APIs
Separate open-state ownership, accessible dialog behavior, content composition, and application-level stacking.
Offer controlled open/onOpenChange plus an optional uncontrolled default, semantic Trigger/Content/Title/Description/Close parts, and a portal/layer manager. The primitive owns focus, Escape, outside interaction, scroll locking, labelling, and restoration. Product code owns business actions and copy. Define nested-dialog and route-change behavior; avoid an imperative global openModal payload that erases type and ownership boundaries.
API design
A component is too generic when its prop combinations encode several unrelated products and invalid states.
Warning signs include boolean prop matrices, callbacks for every internal step, styling escape hatches that bypass invariants, and branches for unrelated domains. Split stable primitives from domain components, encode valid variants, and use composition at deliberate seams. Reuse should reduce coordinated change; if every caller needs exceptions, the abstraction has grouped code by visual similarity rather than shared behavior.
Application boundaries
Keep UI dependent on small domain contracts rather than importing transport, storage, analytics, and framework details everywhere.
Define a domain-facing contract for operations and state, then provide adapters for HTTP, local storage, analytics, or framework navigation at a boundary. Components consume the contract through props, context, or a focused Hook. This keeps tests behavioral, migrations local, and server/client differences explicit. Do not introduce an interface for every function; invert volatile infrastructure dependencies that cross many features.
TypeScript
Let data infer the item type and require only the operations the component actually needs.
Declare the component as a generic function, infer T from items, and type callbacks such as getKey(item: T) and renderItem(item: T). Constrain T only when the component relies on a property; otherwise a required accessor keeps the component flexible. Polymorphic as props require careful ref and intrinsic-prop merging and are often unnecessary for domain components.
TypeScript
Give each event a literal type and exact payload, then enforce exhaustive transitions.
Model actions as a discriminated union so each event carries only its valid payload and the reducer narrows automatically. An exhaustive never check makes adding an event without handling it a compile error. Pair that with a state union when loading, success, empty, and error are mutually exclusive. Types cannot prove runtime server data, so validate external inputs at the boundary.
Autocomplete, virtualized data, real-time UI, notifications, modals, chat, and form-builder platforms.
Interview focus: Cover state, APIs, capacity, accessibility, failure, tests, and observability—not just component names.
Autocomplete
Combine an accessible combobox state machine with bounded remote requests, stale-result protection, and observability.
Separate input text, selected value, popup navigation, and remote query state. Debounce request start, abort obsolete work, guard response order, cache by normalized query, cap results, and define empty/error/offline UI. Implement combobox keyboard and screen-reader behavior, preserve focus, and measure input latency, request rate, cache hits, selection success, and errors. React is only one layer of this system.
Large lists
Infinite loading bounds data transfer per request; virtualization bounds mounted UI work.
Infinite scroll loads more data as a sentinel approaches the viewport but can still accumulate thousands of DOM nodes. Virtualization renders only a window regardless of loaded item count. Large feeds often need both, plus cursor pagination, in-flight and end guards, error retry, focus and screen-reader strategy, deep linking, scroll restoration, and an alternative path when endless scrolling harms navigation.
Large lists
Design bounded row/column rendering alongside server queries, keyboard navigation, and reliable selection identity.
Define a column schema and stable row IDs; push large sort/filter/page operations to an API; window visible rows and possibly columns with measured sizes and overscan. Keep selection independent of mounted rows, preserve keyboard navigation and focus as cells recycle, expose table semantics or an accessible grid pattern, and handle resize, sticky regions, loading gaps, export, and scroll restoration. Profile DOM, memory, and interaction latency.
Real-time UI
Decouple transport rate from human-visible render rate and make overload behavior explicit.
Do not call setState for every message. Parse and normalize updates outside broad component state, coalesce by entity, and publish snapshots at an intentional cadence such as animation frames or a lower sampling rate. Use selector subscriptions so only affected widgets update. Bound buffers, drop or aggregate obsolete telemetry, resync after gaps, and expose connection lag. Capacity is messages in, processing cost, retained history, and renders out.
Real-time UI
Model optimistic send state, acknowledgements, ordering, pagination, reconnection, and scroll behavior.
Give every outgoing message a client ID and pending/failed/sent state, reconcile it with server identity, and define ordering under reconnect or clock skew. Page older history without breaking the scroll anchor, virtualize long conversations, coalesce presence/typing updates, and preserve draft state. Include retry idempotency, attachment progress, offline behavior, accessibility announcements, abuse controls, and telemetry for delivery lag and failure.
Notifications
Separate durable notifications from transient feedback and define queueing, deduplication, accessibility, and recovery.
Model durable inbox items separately from transient toasts. Give events IDs, priority, deduplication, expiry, and actions; bound the visible toast queue; pause dismissal for interaction; and use an appropriate live region without announcing a burst of low-value updates. Keep success feedback near the triggering control when possible. Persist read state server-side if it must cross devices and measure delivery and action outcomes.
Platform design
Treat forms as versioned domain schemas with an editor, renderer, validation engine, migration path, and accessible component contract.
Define a versioned schema for fields, layout, validation, conditions, and data mapping; keep it independent from React component implementation. Build an editor with undo/redo and draft persistence, and a renderer that maps schema primitives to accessible design-system components. Validate on client and server, migrate old schemas, sandbox extensions, localize labels/errors, and observe completion and field failure without collecting sensitive values.
Design systems
Build accessible foundations, prove migration value, and create an incremental path with ownership and compatibility.
Inventory usage and pain, establish tokens and a small set of accessible high-leverage primitives, and publish versioned APIs with examples, tests, and migration tooling. Adopt through touched-code and priority-flow migrations rather than a freeze. Provide compatibility wrappers and codemods, measure bundle and defect impact, establish contribution and deprecation governance, and partner with product teams so the system solves real delivery problems.
Microfrontends
Use microfrontends for durable autonomous ownership boundaries, not as a cure for ordinary component modularity.
Microfrontends can align large autonomous teams with independent deployment and bounded domains. They add runtime coordination, duplicated dependencies, routing and state contracts, inconsistent UX, observability complexity, and migration overhead. Prefer a modular monolith and shared build when teams can coordinate. If adopted, define integration boundaries, dependency policy, design-system compatibility, failure isolation, performance budgets, and cross-team contracts before choosing a runtime technique.
XSS, trust boundaries, authentication, authorization, error boundaries, recovery, telemetry, and deployment safety.
Interview focus: Keep sensitive authority on the server, contain failures, and provide a recoverable user path with evidence.
XSS
React escapes text by default; bypassing that boundary requires trusted, context-appropriate sanitization.
React normally escapes interpolated text, but dangerouslySetInnerHTML tells it to insert raw HTML. Untrusted or incorrectly sanitized content can execute XSS through elements, attributes, URLs, or mutation paths. Sanitize on a trusted boundary with an allowlist designed for HTML, keep the dependency updated, consider Trusted Types and CSP as defense in depth, and never treat client sanitization as authorization.
Authorization
Client route guards and hidden buttons improve UX, but every protected operation and response must be authorized server-side.
All client code and state are controlled by the user, so hiding a route or button cannot grant or deny authority. The server must authenticate the request and authorize each operation and returned resource. Choose cookie or token storage based on threat model, CSRF, XSS, and deployment constraints; avoid placing secrets or privileged data in bundles. React renders the UX around security decisions—it is not the enforcement layer.
Supply chain
Minimize trusted code, review upgrades, scan artifacts, and assume every client-shipped value is public.
Keep dependency count and privileges small, pin and review lockfile changes, monitor advisories, generate provenance/SBOM where required, and test updates in staged builds. Restrict third-party scripts with CSP and isolation. Build-time public environment variables and source maps can expose data, so never bundle secrets. Security scanning supports—not replaces—ownership, threat modeling, and rapid patch/rollback capability.
Error handling
Contain render-time failures below a boundary while handling event, network, and server failures in their own paths.
An Error Boundary can catch rendering and lifecycle errors in its descendant tree and render fallback UI. It does not generally catch errors in event handlers, arbitrary async callbacks, server rendering, or the boundary itself; those need normal try/catch, rejected-state handling, framework error routes, or process-level reporting. Place boundaries around recoverable product regions and log component context without exposing sensitive data.
Deployment reliability
Distinguish transient network failure from a stale HTML/new-deployment chunk mismatch and preserve a safe recovery path.
Catch the rejected lazy import at an error boundary, report the asset URL and release, and offer a deliberate retry or reload. A one-time cache-busted retry may help transient failures; repeated automatic retries can loop. Deploy immutable hashed assets with sufficient retention so old HTML still resolves, coordinate CDN invalidation, and avoid deleting prior chunks immediately during rollout.
Observability
Correlate errors, performance, network, releases, and user journeys without collecting sensitive data.
Capture actionable JavaScript errors and component stacks, route and interaction performance, Core Web Vitals, long tasks, failed resources and APIs, and critical journey outcomes. Tag release, route, device class, and trace IDs so client and backend evidence joins. Sample high-volume signals, scrub personal data, define alert thresholds around user harm, and preserve source-map access securely. Telemetry must support rollback decisions, not merely dashboards.
Real incidents involving render storms, slow input, memory growth, hydration, stale requests, large DOMs, and update overload.
Interview focus: Protect the user, collect a trace, narrow the failing layer, mitigate safely, and verify recovery.
Render diagnosis
Measure the interaction, locate the update owner, and narrow render reach before applying memoization.
Reproduce with realistic data and record a React profile. Identify which state or context update starts the commit, why unaffected widgets render, and whether expensive work or browser layout causes the delay. Move filter draft state to the smallest owner, split broad context, derive stable props, and memoize only measured expensive boundaries. Reprofile the same interaction and verify correctness, latency, and memory.
Interaction latency
Separate input control, expensive render, synchronous computation, request churn, and browser layout in one trace.
First determine whether the input's value update is blocked or only results lag. Record a browser trace and React profile while typing. Look for synchronous filtering, a high-owned state update rendering a large tree, request work on every keystroke, layout/paint, or third-party handlers. Keep input state urgent and local, defer or transition expensive results, debounce requests where appropriate, cancel stale work, and verify input latency.
Memory
Prove retained ownership with repeated navigation, heap evidence, and cleanup inspection rather than blaming React generically.
Create a repeatable navigate-away cycle and compare heap snapshots after forced idle/GC in a profiling environment. Inspect retained component data, detached DOM, event listeners, timers, WebSocket or observer subscriptions, global registries, query caches, and closures. Confirm Effect cleanup and library disposal. Separate expected cache warm-up from unbounded growth, fix the retaining owner, and rerun enough cycles to verify a stable plateau.
State architecture
Separate cohesive responsibilities and notification scopes rather than wrapping the same giant value in useMemo.
Profile the provider updates and consumer set, then split values by ownership and change frequency—for example auth identity, theme, notification commands, and dashboard data. Narrow provider placement and separate state from stable actions where useful. Stabilize values only after the boundary is correct. Move high-frequency shared data to an external store with selectors so consumers subscribe to the exact slice they render.
Loading performance
Inspect transfer, parse/compile, hydration, component work, and third-party long tasks along the actual device path.
Test a production build on representative mobile hardware and network. Trace bundle download, parse, compile, module initialization, hydration, React commits, long tasks, and third-party scripts. Use coverage and bundle analysis to remove or split code, move noninteractive work server-side where appropriate, stream useful HTML, defer third parties, and reduce hydration scope. Track interaction latency and errors, not only first paint.
Async races
An older request completes after a newer request and wins a last-write race.
The input may start request A and then B; network completion order is independent, so A can arrive last and overwrite B. Abort obsolete requests to save work and guard every completion against the latest query or request sequence for correctness. Debounce request creation, not input rendering. A query library can centralize keys, cancellation, deduplication, cache freshness, and navigation behavior.
Large lists
Bound both loaded data and mounted UI instead of trying to memoize an unbounded tree.
Profile to confirm mount, DOM, layout, or data processing cost, then paginate or cursor-load data and virtualize visible rows with overscan. Move large sort/filter operations to a worker or server when appropriate, preserve stable IDs and selection outside mounted rows, and design focus, screen-reader, sticky-header, and scroll-restoration behavior. React.memo cannot make 50,000 DOM nodes cheap.
Hydration
Compare raw server HTML with the first client tree and narrow nondeterminism by route, release, locale, and environment.
Reproduce with the production build and affected request context. Compare View Source with the browser's parsed DOM and React's first client inputs. Inspect dates, locale, random IDs, browser storage, feature flags, invalid nesting, request data, CDN rewrites, extensions, and mixed-version assets. Bisect the release and component boundary. Restore deterministic first output; do not broadly suppress warnings or switch the route to client-only rendering.
Real-time capacity
Protect the main thread by decoupling ingress rate, state publication, and visual refresh rate.
Measure incoming rate, processing cost, queue depth, render cadence, and acceptable staleness. Coalesce superseded updates by entity, sample telemetry, publish at animation-frame or product-specific intervals, and use selector subscriptions. Bound queues and define drop, aggregate, resync, or disconnect behavior under overload. Move parsing to a worker if it is material, and expose lag so silent stale UI is not mistaken for real time.
Architecture diagnosis
Many effects are not automatically wrong, but intertwined dependencies often expose mixed ownership and indirect state choreography.
Map each Effect to the external system it synchronizes. Remove derived-state and event-driven Effects, combine updates into explicit transitions, and split independent capabilities into components or focused Hooks with clear lifetimes. If several Effects coordinate one workflow, model it as a reducer or state machine. Keep genuinely independent subscriptions separate. Use tests and profiling to preserve behavior during incremental extraction.
Start with the answer you can say aloud. Then trace React behavior, TSX, failure modes, production impact, and the follow-up that turns API knowledge into engineering judgment.
A render is React calling components to calculate the next UI description from props, state, and context. That calculation must stay pure and may be restarted. React then compares the next tree with the previous one, and during commit applies necessary host changes such as DOM mutations and refs. Browser layout and paint happen after React's work; component execution does not imply a DOM update.
A useful explanation starts with the trigger. An initial root render, local state update, ancestor update, consumed context change, or external-store notification schedules work. React calls components to produce the next element tree. Every call observes one fixed snapshot of props and state, which is why render code must remain a deterministic calculation rather than mutate the outside world.
React then relates the next tree to the previous tree. Element types, sibling position, and keys decide which component identities survive. This reconciliation work can call many components even when their eventual host output is unchanged. In concurrent rendering, React may pause, restart, or abandon the calculation; no partially calculated tree becomes visible.
Only commit makes the chosen result observable: React applies host mutations, updates refs, and runs effect lifecycle work. The browser then performs its own style, layout, paint, and compositing. That separation matters in production because a slow interaction may be expensive component calculation, a large commit, or browser layout—and each needs different evidence and a different fix.
function Counter() {
const [count, setCount] = useState(0)
function increment() {
setCount(current => current + 1)
}
return <button onClick={increment}>Count: {count}</button>
}A senior answer maps a symptom to the phase that can produce it. Use React DevTools for component and commit work, the browser trace for task/layout/paint work, and network evidence for delivery. Optimizing the wrong phase is common and expensive.
Each render receives fixed state values. Calling a setter queues work for a future render; it does not change the state variable inside the current event handler or closure. That is why several setCount(count + 1) calls from one render all calculate from the same count. Functional updates express a transition from the queued previous value when the next state depends on it.
React gives each render a snapshot of state. Event handlers and callbacks created by that render close over the same snapshot, so calling a setter cannot rewrite the variable they already captured. The setter queues either a replacement value or an updater function for the next render.
Batching lets React process several queued updates before rendering. Three replacement updates calculated as count + 1 from one snapshot all request the same value. Three updater functions each receive the pending result of the previous updater, so they compose. This is queue semantics, not a timer trick and not mutation.
The model explains stale timers and requests too: a callback created during render N still sees render N. The fix depends on intent—functional updates for transitions from prior state, truthful dependencies when synchronization must restart, Effect Events for non-reactive Effect logic in React 19.2, or a ref for deliberately non-rendering mutable bookkeeping.
function Counter() {
const [count, setCount] = useState(0)
const addOne = () => {
setCount(count + 1)
setCount(count + 1)
setCount(count + 1) // final value: 1
}
const addThree = () => {
setCount(value => value + 1)
setCount(value => value + 1)
setCount(value => value + 1) // final value: +3
}
return <><button onClick={addOne}>+1</button><button onClick={addThree}>+3</button></>
}React associates state with a component identity in the rendered tree, determined primarily by element type, position among siblings, and key. If that identity remains stable, React preserves state across renders. If the type or key changes, or the component moves to a different identity position, React treats it as a different instance, cleans up the old one, and initializes new state.
React does not store state inside a function declaration. It stores state for an identity position in the rendered tree. Element type, sibling position, and key tell React whether the next element represents the same conceptual component. Stable identity preserves state, DOM association, refs, and the ongoing Effect lifecycle.
Changing the type or key creates a new identity. React cleans up the old subtree and mounts the new one. This can be intentional—for example, keying an edit form by customer ID to reset its draft—but unstable keys, index keys in reorderable lists, or component definitions created inside render produce accidental remounts.
At architecture scale, ask what lifetime state belongs to. A chat draft may belong to a conversation ID rather than a screen position; selection in a virtualized table must survive row unmounting; a route cache may preserve a subtree intentionally. Identity is a product-lifetime decision, not only a list-warning fix.
type Contact = { id: string; name: string }
function ContactEditor({ contact }: { contact: Contact }) {
return <EditForm key={contact.id} initialContact={contact} />
}
// Selecting another contact creates a new form identity and draft.
// Do this deliberately; unstable keys would lose user input accidentally.Every render creates functions that close over that render's props and state snapshot. If a timer, listener, promise callback, or memoized function runs later, it still sees those captured values unless it is recreated with correct dependencies or reads the latest value through an intentional pattern. Fix the ownership or dependency model; do not suppress the linter and hope the closure stays current.
JavaScript closures retain the lexical values from the render that created them. React does not replace the inside of an old function when state changes; the next render creates new functions that close over the next snapshot. A timer, event listener, promise callback, or memoized function registered earlier can therefore observe old values.
The correct fix depends on why the callback exists. A state transition based on prior state should use a functional updater. An Effect that synchronizes a listener should declare reactive dependencies and resubscribe, or in React 19.2 move non-reactive event-like logic to useEffectEvent. A ref is appropriate when the external callback deliberately needs the latest value without making it render state, but it moves responsibility outside React's reactive checking.
Suppressing exhaustive-deps or wrapping everything in useCallback often freezes the bug in place. Trace the callback to its originating render, name which values should be reactive, and choose an architecture whose lifetime matches the external system.
function Counter() {
const [count, setCount] = useState(0)
useEffect(() => {
const id = window.setInterval(() => {
setCount(current => current + 1)
}, 1000)
return () => window.clearInterval(id)
}, [])
return <output aria-live="polite">{count}</output>
}An Effect describes how a committed render synchronizes with an external system such as a subscription, browser API, timer, network connection, or third-party widget. React runs setup after commit, runs cleanup before resynchronizing with changed dependencies, and cleans up on unmount. If there is no external system, the code probably belongs in render or an event handler.
An Effect is a synchronization process caused by rendering. Setup connects the committed component state to something React does not own: a socket, media player, timer, observer, subscription, network connection, analytics integration, or third-party widget. The dependency list describes which reactive inputs require that process to resynchronize.
React calls cleanup before a changed setup and on unmount. Development Strict Mode performs an extra setup-cleanup-setup cycle so missing teardown becomes visible. If that breaks the integration, the setup and cleanup are not symmetric. A ref flag that prevents the second setup merely hides the lifecycle defect.
Effects are not the default home for computation or user actions. Derived values belong in render, and a purchase or form submission belongs in its event path. Removing an Effect often eliminates a stale intermediate commit, an extra network request, and a source of dependency bugs at the same time.
function Room({ roomId }: { roomId: string }) {
useEffect(() => {
const connection = connectToRoom(roomId)
connection.open()
return () => connection.close()
}, [roomId])
return <h2>Room {roomId}</h2>
}fullName is fully determined by current props, so calculate it during render. Storing it creates another source of truth: React first commits stale fullName, the Effect sets it, and React renders again. The dependency array only maintains the synchronization problem. Reserve state for independent information and Effects for external systems.
The fullName example has no external system and no independent state. firstName and lastName already determine the answer. Storing fullName makes React first render and commit an obsolete value, then run the Effect, queue another update, render again, and commit the correction. That is both extra work and an inconsistent source-of-truth model.
Calculate cheap derived data directly in the component. If a calculation is proven expensive and its inputs often repeat, memoization may cache it, but useMemo remains an optimization. If a value represents an editable draft that intentionally diverges from props, name that product behavior and define when it resets rather than calling it derived state.
type NameProps = { firstName: string; lastName: string }
function Name({ firstName, lastName }: NameProps) {
const fullName = [firstName, lastName].filter(Boolean).join(" ")
return <span>{fullName}</span>
}Effects can start request A, then request B after input changes; if A finishes last, it can overwrite B's newer result. Cleanup should abort obsolete work where supported, and the completion path must ignore results that no longer match the active request. In production, prefer a framework loader or query/cache layer that also handles deduplication, freshness, retries, and navigation races.
Network completion order is unrelated to the order in which requests started. If selecting Alice starts request A and selecting Bob starts request B, A can finish last and overwrite Bob's current result. An Effect cleanup runs when person changes, which gives the previous request a place to abort and marks its completion as obsolete.
Cancellation and correctness are related but distinct. AbortController can stop supported fetch work and save resources, but a response may already have completed or a promise continuation may already be queued. The resolution path should also prove it still represents the active request—using an ignore flag, sequence number, or cache key before committing data.
At product scale, route loaders and query caches centralize this problem with request identity, deduplication, freshness, retries, navigation cancellation, and shared results. A hand-written Effect is an implementation option, not the default architecture for all remote state.
useEffect(() => {
const controller = new AbortController()
let obsolete = false
void fetchUser(personId, { signal: controller.signal })
.then(user => {
if (!obsolete) setUser(user)
})
.catch(error => {
if (error.name !== "AbortError" && !obsolete) setError(error)
})
return () => {
obsolete = true
controller.abort()
}
}, [personId])Keys tell React which sibling in the next list corresponds to which sibling in the previous list. A stable domain key lets state and DOM association follow an item through insertion, removal, and reordering. Keys need only be unique among siblings, but they must be stable across renders. Generating a new key during render makes every item appear new and resets state.
Within one sibling set, React needs a stable way to relate logical items across renders. Without an explicit key, position is the fallback identity. That works only while the list's logical identity matches position. Insertions, deletion, filtering, or sorting break the relationship and can move local state, uncontrolled input values, focus, and animation state onto the wrong item.
A good key comes from the domain record and remains stable for that record across renders. It does not need global uniqueness and is not passed as a normal prop. A changed key is an instruction to replace the identity, so it is also a useful deliberate reset mechanism when the product lifetime changes.
type Task = { id: string; title: string }
function TaskList({ tasks }: { tasks: Task[] }) {
return (
<ul>
{tasks.map(task => (
<TaskRow key={task.id} task={task} />
))}
</ul>
)
}Reproduce the exact interaction with production-like data and record a browser Performance trace plus React DevTools profile. Determine whether time is in JavaScript, React rendering, a commit, layout/paint, network, or third-party work. Identify why the expensive subtree rendered, form one hypothesis, make the smallest architectural or algorithmic change, and remeasure the user-visible latency and regressions.
Begin with a user-visible symptom and a repeatable interaction under production-like data. A vague report that React is slow cannot distinguish network delay, input blocking, JavaScript work, component rendering, DOM mutation, layout, paint, memory pressure, or third-party code. Capture a browser Performance trace and a React DevTools profile for the same interaction.
In React's profile, identify the relevant commits, expensive components, and why they rendered. In the browser trace, locate long tasks, script stacks, style/layout, paint, and network timing. Broad component work often points to state or context placed too high; a single expensive component may contain algorithmic work; a small React commit followed by large layout points to DOM/CSS architecture.
Form one hypothesis and change one limiting factor: move state, change the algorithm, split context, virtualize, cache a proven calculation, reduce DOM, or move blocking work. Then rerun the same trace. Report both improvement and cost—memory, code complexity, stale-data risk, or delayed work—because an optimization that only moves latency is not complete.
Staff-level performance work adds budgets, representative lab and field telemetry, release comparison, ownership, and a rollback threshold. The durable output is not one faster component; it is a system that catches regressions before users do.
Use windowing: render only visible rows plus a small overscan buffer, represent total height with spacers, and recycle positions as the viewport moves. Define stable item identity, row measurement for variable heights, keyboard and screen-reader behavior, focus retention, and scroll restoration. Pagination bounds data transfer; virtualization bounds DOM and render work. React.memo alone leaves the huge DOM mounted.
A 100,000-row problem is an unbounded-work problem, not primarily a memoization problem. Even if every row component bails out, the browser still owns a huge DOM, style calculation, layout structures, accessibility tree, and memory footprint. Virtualization bounds the mounted representation to the viewport plus overscan.
A virtualizer maps scroll offset to a visible item range, positions those rows inside a container that represents total size, and recycles positions as the user moves. Fixed-height rows are straightforward; variable-height content needs measurement, estimation, and correction without visible jumps. Stable domain identity must remain separate from recycled visual slots.
Production design includes more than speed: keyboard navigation cannot lose focus when a row leaves the window; assistive technology needs meaningful position and count semantics; selection must survive unmounting; sticky headers, expanded rows, deep links, print/export, and scroll restoration need explicit behavior. Pagination controls data transfer, while virtualization controls rendering and DOM size.
Choose a capacity budget: maximum loaded records, mounted rows, retained selection, and scroll-history depth. Validate on lower-end devices and define a non-virtual fallback for print, export, or assistive workflows when needed.
Concurrent rendering lets React prepare multiple versions of UI with priority-aware, interruptible render work. A lower-priority render can pause or restart so an urgent input update stays responsive; only a completed consistent tree commits. It does not mean component functions run simultaneously on multiple JavaScript threads. Purity is what makes restartable calculation safe.
Concurrent rendering is a scheduling capability. React can prepare a lower-priority tree, pause it when urgent work arrives, restart with newer inputs, and commit only a completed consistent result. JavaScript component code is not executing in parallel across CPU cores; the benefit is that render work no longer has to monopolize the main thread until completion.
Transitions mark updates whose visual destination can wait while urgent input remains responsive. Deferred values let one part of the tree temporarily use an older value while a lower-priority render catches up. Suspense boundaries coordinate what can remain visible and what fallback appears when compatible work is not ready.
Concurrency does not fix a 200 ms synchronous calculation: a long uninterrupted JavaScript task still blocks. Reduce or move that work, virtualize large UI, or use a worker where appropriate. Purity remains essential because React must be free to repeat or discard render calculations without leaking side effects.
function Search({ items }: { items: string[] }) {
const [query, setQuery] = useState("")
const [isPending, startTransition] = useTransition()
const [filter, setFilter] = useState("")
function updateQuery(next: string) {
setQuery(next)
startTransition(() => setFilter(next))
}
const matches = items.filter(item => item.includes(filter))
return <SearchView query={query} onQueryChange={updateQuery} items={matches} pending={isPending} />
}Version context: React Compiler 1.0 became stable in October 2025 and supports incremental adoption; integrations remain build-tool specific.
React Compiler 1.0 is a stable build-time optimizer that analyzes component and Hook code and applies granular memoization automatically. It reduces routine useMemo, useCallback, and React.memo work, but it does not fix expensive algorithms, huge DOMs, network waterfalls, or broken state ownership. Adopt it incrementally, respect the Rules of React, test behavior, and verify performance rather than assuming every component improves.
React Compiler 1.0 is a stable build-time optimizer. It analyzes data flow and mutability in components and Hooks, validates Rules-of-React assumptions, and inserts granular memoization where its analysis determines values or JSX can be reused. This can be more precise than manually wrapping whole components or calculations.
The compiler changes the default conversation around useMemo, useCallback, and React.memo: new compiler-enabled code can usually rely on automatic optimization, while manual memoization remains available for precise identity requirements and existing code should not be stripped mechanically. Correctness must never depend on a cache React is allowed to discard.
It does not solve algorithmic complexity, network waterfalls, oversized bundles, huge DOMs, layout cost, broad state ownership, or third-party work. Adoption still needs compatibility linting, representative tests, performance baselines, staged rollout, exact version policy where risk warrants it, and an escape hatch for incompatible code.
Treat compiler adoption as a platform change: compatibility inventory, lint gates, representative performance cohorts, release pinning, escape-hatch ownership, and upgrade playbooks matter more than a before/after demo component.
Version context: Server Components are stable in React 19; production routing, caching, server functions, and bundling are supplied by a framework, whose underlying RSC APIs require careful version alignment.
Server Components render ahead of the client bundle in a separate environment, can access server-side data, and send rendered output rather than their component code to the browser. Interactive state, Effects, and browser APIs belong in Client Components. Values crossing the boundary must be serializable by the integration. React 19 stabilizes the component model, while bundler/framework implementation APIs can still change between minors.
A Server Component executes in a server-like build or request environment and does not ship its component code to the browser. It can access server data and libraries, await during rendering, and pass serializable props or rendered children into Client Components. Client Components own interaction, state, Effects, and browser APIs; they can still participate in server HTML rendering through a framework.
Server Components and SSR are orthogonal. SSR produces initial HTML; RSC changes the component module graph and payload so some components never enter the client bundle. A framework composes routing, caching, data mutations, streaming, deployment, and bundling around React's model. The React 19 Server Component contract is stable, while the low-level bundler/framework implementation APIs can change between React minor versions.
Move a boundary only when it improves data locality, removes meaningful client JavaScript, or protects server-only dependencies while preserving the product interaction. Serialization, extra server work, navigation latency, cache invalidation, and network boundaries are costs. Any data passed to the client is client-visible, so the boundary is not automatic authorization.
Hydration lets React attach behavior to server-rendered HTML while reusing that DOM. The first browser render must match the server output in structure, text, attributes, and stable IDs. Dates, locale, random values, browser-only storage, invalid HTML, request-specific data, extensions, and CDN rewrites can diverge. Find and remove the nondeterministic source; suppression is only a narrow escape hatch.
Server rendering sends HTML the browser can display before all client code is ready. Hydration creates a React root over that existing HTML, matches it to the first client render, attaches event behavior, and makes the tree interactive. The contract is deterministic first output: equivalent structure, text, attributes, and IDs for the same boundary.
Mismatches come from values that differ by environment or time—Date, Math.random, locale, browser storage, viewport APIs, request data, feature flags—or from invalid HTML that the browser reparses. Extensions and CDN transforms can mutate the document before React sees it. A Client Component in a framework may still contribute to server HTML, so a typeof window branch during render can produce two different trees.
Fix the source by passing stable server values, delaying browser-only state until after hydration, using useId for stable accessibility IDs, and correcting invalid markup. suppressHydrationWarning is a narrow one-level escape hatch for unavoidable leaf differences; it is not a repair strategy. Production diagnosis compares raw response, parsed DOM, first client inputs, route, locale, release, and asset versions.
Use the ARIA combobox pattern with a labelled input, controlled popup state, listbox relationship, active option indication, and documented Arrow, Enter, Escape, and Tab behavior. Keep DOM focus predictable, announce result count/loading changes without noise, preserve typed text, and expose errors. Add debounce, cancellation, stale-result guards, caching, and mobile/touch behavior without breaking keyboard semantics.
An autocomplete is a composite interaction, not an input plus an absolutely positioned list. Model text input, popup visibility, active option, selected value, results, loading, empty, error, and composition/input-method behavior explicitly. The input keeps a reliable accessible name and the popup follows the WAI-ARIA combobox pattern.
Keyboard behavior includes Down/Up navigation, Enter selection, Escape dismissal, Tab movement, and predictable Home/End behavior where applicable. Decide whether DOM focus stays on the input with aria-activedescendant or moves into options, then implement the pattern consistently. Announce result count or loading changes through a restrained live region without repeating every keystroke.
Remote behavior needs debounce, AbortController, an arrival-order guard, a minimum query policy, bounded results, cache identity, and clear failure/retry. Track time to first useful suggestion, stale response drops, request rate, selection completion, and accessibility errors. Link to the existing JavaScript typeahead controller for transport mechanics rather than duplicating it here.
Create a repeatable navigate-away cycle and compare heap snapshots after forced idle/GC in a profiling environment. Inspect retained component data, detached DOM, event listeners, timers, WebSocket or observer subscriptions, global registries, query caches, and closures. Confirm Effect cleanup and library disposal. Separate expected cache warm-up from unbounded growth, fix the retaining owner, and rerun enough cycles to verify a stable plateau.
First prove the shape of growth. Repeatedly navigate into and out of the screen with a fixed workload, allow idle time, and compare heap snapshots or allocation timelines. A warm cache may reach a plateau; a leak keeps retaining each screen generation. Browser memory includes JavaScript heap, DOM, images, workers, WebAssembly, and native library allocations, so one metric is not enough.
Trace retaining paths for detached DOM nodes, component data, listeners, timers, observers, sockets, promises, and closures. Inspect Effect cleanup, but also global event buses, module-level registries, query caches, state stores, portal roots, third-party charts, and pending queues. React cannot free an object while another owner still references it.
Fix the ownership boundary: unsubscribe, clear, abort, dispose library instances, bound caches and history, or change worker lifecycle. Do not call garbage collection as a fix; GC cannot collect reachable objects. Verify with the same repeated cycle until memory reaches a stable range and confirm that cleanup did not break reconnection or navigation behavior.
Production readiness adds memory telemetry by route and release, long-session synthetic tests, cache budgets, disposal contracts for third-party components, and safe worker recycling only as mitigation—not as a substitute for root cause.
Measure incoming rate, processing cost, queue depth, render cadence, and acceptable staleness. Coalesce superseded updates by entity, sample telemetry, publish at animation-frame or product-specific intervals, and use selector subscriptions. Bound queues and define drop, aggregate, resync, or disconnect behavior under overload. Move parsing to a worker if it is material, and expose lag so silent stale UI is not mistaken for real time.
A transport can deliver data faster than a person can perceive and faster than the main thread can parse, reduce, render, and paint it. Calling setState for every message couples ingress rate directly to React work, grows queues during bursts, and can make the dashboard both delayed and misleading.
Insert a bounded processing layer between transport and UI. Normalize messages, coalesce superseded updates by entity, aggregate or sample telemetry, and publish immutable snapshots at a deliberate cadence such as requestAnimationFrame or a product-specific interval. Selector subscriptions let each widget render only the slice it uses. Move expensive parsing or aggregation to a worker when the measured main-thread cost justifies it.
Backpressure requires a policy: when the buffer is full, drop obsolete point updates, aggregate them, ask the server for a slower stream, disconnect and resync, or shed lower-priority channels. Track inbound rate, buffer depth, dropped/coalesced count, snapshot age, render cadence, and end-to-end lag so the interface can disclose stale data instead of silently presenting it as live.
Staff reasoning covers an end-to-end capacity contract: server publish policy, transport sequence, client buffer, aggregation, render budget, reconnect/resync, and SLOs for freshness rather than raw message delivery.
Map each Effect to the external system it synchronizes. Remove derived-state and event-driven Effects, combine updates into explicit transitions, and split independent capabilities into components or focused Hooks with clear lifetimes. If several Effects coordinate one workflow, model it as a reducer or state machine. Keep genuinely independent subscriptions separate. Use tests and profiling to preserve behavior during incremental extraction.
The count alone is not the defect; fourteen independent integrations could legitimately require fourteen Effects. The smell is intertwined dependencies and Effects that set state only to trigger other Effects. That pattern usually means one component owns several workflows, derived values have been stored, and event transitions became indirect synchronization chains.
Create an Effect inventory. For each one, name the external system, setup, cleanup, and reactive identity. Delete derivation Effects, move user-caused actions to event handlers, consolidate atomic domain transitions in a reducer or state machine, and extract coherent external lifecycles into focused Hooks or child components. Keep independent subscriptions separate so changing one does not restart another.
Refactor incrementally behind behavior tests and production telemetry. Splitting a large component into files without changing ownership only relocates the coupling. The desired outcome is a smaller number of understandable state machines and synchronization boundaries, not a target Effect count.
At Staff level, repeated Effect tangles across teams often indicate missing domain APIs, data-layer policy, or platform primitives. Fix the shared boundary and migration path, not only one component.
Foundational questions test durable rendering models; intermediate questions test state and component design; senior questions test performance and production diagnosis; Staff questions test platform, migration, capacity, and organizational judgment. Level labels are not claims about any employer's proprietary interview frequency.
Pair the render model with JavaScript scheduling, browser performance, distributed reliability, and architecture judgment. React is one layer of the production system.