skip to content

Rendering and Reconciliation

How React turns your components into DOM updates: what schedules a render, how the fiber tree is built and diffed, when the result is committed, and how concurrent rendering can pause the whole thing. Interviewers reach for this area when they want to know whether you can reason about re-render and stale-state bugs instead of guessing.

part ofReactoverview, primer and where to startread it →
on this pageshow

explore

questions

page 1 of 2

In a React 19 component, one click handler calls setCount, setName and setOpen one after another, yet the component body logs only one render. What is automatic batching, and why does React work this way?

level: juniorimportance: must knowfreq 72%

answer

  1. setters queue, they do not render
  2. one logical action, one render pass
  3. intermediate UI nobody should see
  4. the batch closes when the work returns
  5. escape hatch exists but is rare

basics

~20 s

Automatic batching means React collects every state update queued while the current work runs and applies them in a single re-render instead of rendering once per setter. It avoids wasted renders and half-updated intermediate UI.

solid answer

~50 s

Calling a state setter does not render immediately — it queues an update on the component. React waits until the currently running work (the event handler, in this case) finishes, then performs one render pass that reflects all three updates at once. That is automatic batching. The motivation is both performance and correctness: three renders would do three times the reconciliation and commit work for a UI nobody ever sees, and the intermediate renders would show a state combination that never logically existed — a new `count` next to the old `name`. Batching also spans components: if the handler updates state in a parent and in a child, React still re-renders the affected tree once. It does not merge the values into a single object the way class `this.setState` did, and it does not make the setters synchronous — the DOM is not updated until after the handler returns.

code

jsx · 18 lines
jsx
import { useState } from 'react';

export default function Panel() {
  const [count, setCount] = useState(0);
  const [name, setName] = useState('anon');
  console.log('render', count, name);

  function handleClick() {
    setCount(count + 1);
    setName('ada');
  }

  return (
    <button onClick={handleClick}>
      {name}: {count}
    </button>
  );
}

go deeper

for a junior

Be ready to say plainly that setters queue updates and React re-renders once when the handler finishes, and to predict the render count for a handler that calls three setters.

for a middle

Explain where the batch boundary sits — the end of the running work, not the end of each setter — and why an unbatched intermediate render would expose a state combination the UI was never designed for.

for a senior

Show that you reason about batching when debugging: unexpected render or effect counts, code that reads the DOM too early, and the rare place where forcing a synchronous commit is genuinely justified.

for a principal

Frame batching as a scheduling contract the framework owns: application code should state intent and let React choose when to flush, and every opt-out you allow in the codebase is a place that constrains future scheduling work.

## The mental model A React state setter is a *request*, not an assignment. When you call `setCount(c + 1)`, React records an update against that component and schedules work; it does not stop and re-render on the spot. Automatic batching is the rule that decides *when* that scheduled work actually runs: React waits until the synchronous code that queued the updates has finished, and then renders once with all of them applied. So a handler like this produces exactly one render: ```jsx function Panel() { const [count, setCount] = useState(0); const [name, setName] = useState(''); const [open, setOpen] = useState(false); console.log('render', count, name, open); function handleClick() { setCount(count + 1); // queued setName('ada'); // queued setOpen(true); // queued } // handler returns -> React renders once return <button onClick={handleClick}>go</button>; } ``` The `console.log` in the body fires twice in total across the interaction: once for the initial render, once after the click. Never three times. ## Why React does it **Performance.** Each render pass means re-running the component functions in the affected subtree, diffing the resulting elements against the previous tree, and committing the differences to the DOM. Doing that three times for one logical user action triples the work, and two thirds of it produces pixels the user never sees, because the browser cannot paint in the middle of your synchronous handler anyway. **Consistency.** This is the part candidates usually miss. Without batching, an intermediate render would exist in which `count` had been updated but `name` had not. That combination is not a state your UI was ever designed to be in. Anything reading both — a derived value, a conditional, a child that receives both as props — would briefly see an incoherent pair. Batching means every render observes a *coherent snapshot*: all the updates from one logical action, applied together. **Scope.** The batch is not per-component. If a click handler calls a setter for the parent and a setter passed down to a child, React still does a single render pass over the affected part of the tree. It is one pass over the work, not one pass per state variable. ## Three things batching is not **It is not object merging.** In legacy class components, `this.setState({a: 1})` shallow-merged a partial object into `this.state`. With `useState` there is no merge — each setter owns one independent value. Batching is about *how many renders happen*, not about combining values into one object. **It does not make the setter synchronous.** The DOM has not changed when the setter returns, and it has not changed when the handler returns either — React renders and commits after the handler, before the browser paints. Code that reads the DOM immediately after a setter sees the old DOM. **It is not limited to event handlers.** Since React 18 (and therefore in React 19) React batches updates wherever they are queued — inside a promise callback, a `setTimeout`, a handler added with `addEventListener`. Older React batched only inside its own event handlers, which is why upgrade guides talk about "automatic" batching: the automatic part is that you no longer have to do anything to get it. ## Opting out Occasionally you need the DOM updated *before* the next line of imperative code runs — for example you append an item and then immediately want to scroll it into view. `flushSync` from `react-dom` forces React to render and commit the updates queued inside its callback before it returns. It is an escape hatch: it costs a synchronous render and gives up the scheduling flexibility batching buys you, so it is rare in application code. ## What interviewers listen for A weak answer says "React is asynchronous" and stops. A good answer names the two payoffs — fewer render passes and no incoherent intermediate state — states that the batch boundary is the end of the running work rather than the end of each setter, and knows the escape hatch exists without reaching for it by default.

  • Does batching mean the DOM is already updated by the time the click handler returns?
    No. React renders and commits after the handler finishes, before the browser paints. Inside the handler — and on the line right after the last setter — the DOM still shows the previous output. If you truly need the committed DOM before continuing, `flushSync` from `react-dom` is the only supported way to force it.
  • If a handler updates state in a parent and in a child, is that one render or two?
    One render pass. Batching is scoped to the scheduled work, not to a component, so React reconciles the affected part of the tree once with both updates applied. That is also why batching improves consistency: the parent and child never render out of step with each other.
  • Does batching change how many times my effects run?
    Yes, and that is usually what you want. Effects run after the commit, so one batched render means one round of effect cleanup and re-run instead of three. Code that assumed an effect would fire once per setter is relying on unbatched behavior, which is a bug rather than a feature.

It is like a waiter taking every order at the table before walking to the kitchen, instead of making one trip per dish. Fewer trips, and the whole table is served a consistent meal.

saying these in an interview costs you the question

  • Says setState is asynchronous because it returns a promise
  • Claims batching merges the state values into one object
  • Expects the DOM to be updated on the line after the setter
  • Thinks each useState setter always triggers its own render
  • Believes batching only ever applies inside React event handlers

context

open as a page

In a React function component that renders <input ref={inputRef} />, reading inputRef.current in the component body gives null, but reading it inside an effect gives the DOM element. What happens in between?

level: juniorimportance: must knowfreq 60%

basics

~20 s

Rendering only computes what the UI should be, so no DOM node exists yet and the ref is still null. React then commits that output: it creates the DOM nodes, attaches refs to them, and only afterwards runs your effects.

open as a page

In a React 19 search box, the onChange handler calls setQuery(value) directly but wraps setFilterTerm(value) in startTransition. Why must the input's own state update stay outside the transition?

level: juniorimportance: must knowfreq 55%

basics

~20 s

The typed value is urgent: React must commit it right away or the characters the user typed appear late. An update marked with startTransition is non-urgent and interruptible, so putting the input's own value in one makes the field feel stuck.

open as a page

In React, the element at one position in the tree is a <div> on one render and a <section> on the next, with identical children inside. What does React do with that node and everything beneath it?

level: juniorimportance: must knowfreq 68%

basics

~20 s

React tears down the old subtree and builds a new one: DOM nodes are recreated, component state is lost, effect cleanups run and refs detach. Types must match at a position for React to update in place.

open as a page

React is usually described as using a "virtual DOM". Concretely, what is that virtual DOM made of, and is updating it faster than updating the real DOM directly?

level: juniorimportance: must knowfreq 80%

basics

~20 s

React's virtual DOM is a tree of plain JavaScript objects describing the intended UI. They are cheap to create, so React can compare them and write only the DOM that changed — not faster than optimal manual DOM work, just easier.

open as a page

In React, what actually causes a component's function to run again? Candidates often answer "when its props change" — say what really schedules a re-render and why props are not an independent trigger.

level: juniorimportance: must knowfreq 82%

basics

~20 s

React re-runs a component when its own state changes, when a context it reads receives a new value, or when its parent re-renders. Props changing is not a separate trigger — it is a consequence of the parent running again.

open as a page

In a React component, a click handler calls setCount(count + 1) and then console.log(count) on the very next line, and the log still shows the old number. Why does count not change inside that handler?

level: juniorimportance: must knowfreq 82%

basics

~20 s

React state is a snapshot fixed for the whole render. count is a const captured when that render ran, and setCount only queues an update for the next render, so lines after it still read the old value.

open as a page

An app is being upgraded from React 17 to React 19. In React 17, two state updates inside a `.then()` callback or a `setTimeout` produced two re-renders; now they produce one. What changed, and what did teams do before that to get batching in async code?

level: middleimportance: must knowfreq 60%

basics

~20 s

React 18 extended batching to every update, not just those inside React event handlers, for apps mounted with createRoot; React 19 keeps that and removes the legacy root entirely. Before 18, teams wrapped async updates in ReactDOM.unstable_batchedUpdates.

open as a page

React splits an update into a render phase and a commit phase. Walk through what React does during the commit phase, in order, and say where the browser's paint fits into that sequence.

level: middleimportance: must knowfreq 55%

basics

~20 s

React commits in three sub-phases: it takes pre-mutation snapshots, then mutates the DOM and detaches old refs, then attaches new refs and runs layout effects. The browser paints after that, and useEffect callbacks flush later.

open as a page

In React 19's concurrent rendering, what is tearing, and how can a single committed screen end up showing two different values that both came from the same shared source?

level: middleimportance: must knowfreq 42%

basics

~20 s

Tearing is a UI inconsistency where one committed React tree shows two different values read from the same external source, because React paused mid-render, the source changed during the pause, and later components read the newer value.

open as a page

In a React 19 app, a large list is mid-render inside a transition when the user types another character into the search input. React shows the new character immediately and updates the list afterwards. What does React do internally so that one pending update jumps ahead of another?

level: middleimportance: must knowfreq 58%

basics

~20 s

React stamps each update with a priority lane when the setter is called: a keystroke gets an urgent lane, a transition a low one. The scheduler renders the highest pending priority first, discards the unfinished transition render, and restarts it afterwards.

open as a page

A teammate replaces a 300 ms debounce on an expensive filtered list with React 19's startTransition and says the two are equivalent. How do they actually differ in what work runs and what the user sees?

level: middleimportance: must knowfreq 68%

basics

~20 s

A debounce postpones starting the work until the user pauses; a transition starts it immediately but at a priority React can interrupt and discard. So a transition shows fresh results as fast as the machine allows and never inserts an artificial wait, while a debounce is a fixed guess at how long to stall.

open as a page

React keeps two kinds of internal objects for your UI: React elements and fiber nodes. What is each one, and which of them survives from one render to the next?

level: middleimportance: must knowfreq 52%

basics

~20 s

A React element is a throwaway plain object describing what you want at a position: type, props, key. A fiber is React's long-lived internal node for that position, holding state, work flags and the DOM handle. Elements are recreated each render; fibers persist.

open as a page

When a React parent re-renders a list of children, how does the reconciler decide which fiber from the previous render corresponds to each new element — with keys and without them?

level: middleimportance: must knowfreq 70%

basics

~20 s

Without keys, React pairs children by position: slot one with slot one. With keys, it pairs by key, walking both lists in parallel while the keys agree and then looking up the remaining previous children in a key-based map.

open as a page

In React, a component of the same type is rendered with key="a" on one render and key="b" on the next, with identical props. What does the reconciler do to its fiber, its hook state, its DOM node and its effects?

level: middleimportance: must knowfreq 58%

basics

~20 s

The old fiber cannot be reused, so React deletes it and mounts a new one: effect cleanups run, refs detach, the DOM node is removed, and the replacement mounts with fresh hook state and new DOM. The whole subtree is rebuilt.

open as a page

React requires that rendering a component be pure. What does purity mean for a component body concretely, which operations violate it, and what breaks if you ignore the rule?

level: middleimportance: must knowfreq 66%

basics

~20 s

A pure component body computes its output from props, state and context alone, changing nothing that existed before it ran. Mutating props or state, writing to shared variables, reading or writing the DOM, and starting requests during render all violate it.

open as a page

React splits an update into a render phase and a commit phase. What work does React do during the render phase, and what does it deliberately leave out of it?

level: middleimportance: must knowfreq 64%

basics

~20 s

The render phase calls component functions, builds the new element tree, and works out what changed — all in memory, touching nothing outside React. Applying those changes to the DOM, attaching refs, and running effects all belong to the commit that follows.

open as a page

In a React click handler, calling setNumber(number + 1) three times raises the counter by one, but calling setNumber(n => n + 1) three times raises it by three. Describe what React puts in its update queue for each call and how it computes the next state.

level: middleimportance: must knowfreq 72%

basics

~20 s

Each call appends an entry to that state's update queue. A plain value entry replaces the state outright, so three identical values collapse to one result; an updater function entry is called with the result of the previous queued entry, so three of them compose.

open as a page

Before React 16, React's reconciler walked the component tree with recursive function calls that ran to completion. What about that design made pausing mid-render impossible, and what did the Fiber architecture change so React could stop partway and later resume?

level: middleimportance: must knowfreq 58%

basics

~20 s

Recursion kept the traversal state in the JavaScript call stack, which React cannot save or resume. Fiber moves that state into heap objects React owns and replaces recursion with a loop, so React can return control and continue from a saved pointer.

open as a page

In a React app, a text input loses focus after every keystroke and the surrounding widget's state resets. The code declares `function Field() { ... }` inside the parent component's body and renders `<Field />`. What is the mechanism, and how would you fix it?

level: seniorimportance: must knowfreq 55%

basics

~20 s

A component declared inside another component's body is a new function on every render, so the element type at that position differs each time and React remounts it — new DOM node, lost focus, reset state. Declare it at module scope instead.

open as a page

A React list keyed by array index has a new item prepended. Trace what the reconciler does: which fibers are reused, which DOM nodes are created or moved, and where each row's hook state ends up? Contrast that with the same list keyed by a stable item id.

level: seniorimportance: must knowfreq 55%

basics

~20 s

With index keys every slot still matches, so React reuses all fibers and patches each one with the next item's data, appending one new fiber at the end. Hook state stays with the position, not the item. Stable ids instead insert one node and leave the rest untouched.

open as a page

In React 19, does "concurrent rendering" mean React renders components in parallel on several threads or inside a Web Worker? Explain what actually happens on the main thread.

level: juniorimportance: should knowfreq 42%

basics

~20 s

No. Concurrent rendering is interleaving, not parallelism. React still renders on the one main thread; it can split a render into slices, yield to the browser between them, and abandon an in-progress render to do urgent work first.

open as a page

In a React app, two unrelated lists on the same page each give their rows the key values 1, 2 and 3. Is that a problem? Explain the scope in which React actually compares keys.

level: juniorimportance: should knowfreq 45%

basics

~10 s

No. React compares keys only among the children of one parent, during that parent's re-render. Separate lists may reuse the same key values freely; only duplicate keys inside a single list are a bug.

open as a page

In React 19, the render phase can stop partway through the component tree to let the browser handle input or paint. Does that mean a user can end up seeing a partially updated screen, and what happens to the work React had already done before it stopped?

level: juniorimportance: should knowfreq 45%

basics

~20 s

No. React builds the new tree in memory during the render phase and touches the DOM only in the commit phase, which runs as one uninterruptible block. Paused work is either resumed or discarded and redone.

open as a page

In React 19, what does `flushSync` from `react-dom` do to the state updates queued inside its callback, and what is a legitimate reason to reach for it?

level: middleimportance: should knowfreq 46%

basics

~20 s

flushSync(fn) runs fn and then forces React to render and commit the updates it queued to the DOM before flushSync returns, instead of batching them for later. You need it only when the next line of imperative code must see the updated DOM.

open as a page

In a React tree, a parent component and its child both register callbacks with useEffect. On mount, whose callback runs first, and when a single commit changes effects in several components, how does React order the cleanups against the new callbacks?

level: middleimportance: should knowfreq 42%

basics

~20 s

The child's callback runs first: React runs effects bottom-up, deepest component first, so a parent's effect sees fully mounted children. Within one commit React runs every affected component's cleanup before running any of the new callbacks.

open as a page

Why can a value held in React state with useState or useReducer never tear during a concurrent render, while a value read from a mutable module-level object can?

level: middleimportance: should knowfreq 34%

basics

~20 s

React state is versioned, not live: one render pass derives state from each fiber's update queue, so every component it commits sees the same version. A mutable module object is read live, so two reads during one render can differ.

open as a page

During a concurrent render, React's scheduler stops partway through, hands control back to the browser, and continues the render in a later task. What decides when it yields, how does it get control back, and what unit of work can it never break up?

level: middleimportance: should knowfreq 38%

basics

~20 s

React checks between units of work — roughly one component each — whether the current slice has used its small time budget. If so it stops and posts a browser task to continue later. It can never interrupt the inside of one component function.

open as a page

A React 19 component does `const deferredQuery = useDeferredValue(query)` and renders a large list from deferredQuery. Walk through the renders React performs when query changes from 'ab' to 'abc'.

level: middleimportance: should knowfreq 52%

basics

~20 s

React renders twice. First an urgent render in which the hook still returns the old value, so the fast parts of the UI update while the big list stays as it was; then a background, interruptible render in which the hook returns the new value and the list is rebuilt.

open as a page

In React, a text input re-renders on every keystroke, yet the caret keeps its position and focus is never lost. What does React do when the element type at a position is unchanged, and which browser state survives because of it?

level: middleimportance: should knowfreq 52%

basics

~20 s

React keeps the existing DOM node when the element type is unchanged and applies only the props that actually differ. Because the node is never replaced, focus, caret position, scroll offset and media playback all survive.

open as a page

showing 1–30 of 49