skip to content

Element Diffing and Bailouts

React's linear-time diff rests on two bets: different element types produce different trees, and same-type elements can be updated in place. Interviewers probe this to see whether you can explain why a component unmounts and loses all its state when its type or position changes.

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

explore

questions

5

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%

answer

  1. React compares one thing first
  2. different type stops the comparison
  3. rebuild, not patch
  4. state lives with the instance, not the JSX
  5. identical children below do not help

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.

solid answer

~40 s

React compares the element `type` at each position before it compares anything else. A `div` and a `section` are different types, so React stops comparing there and replaces the whole subtree: it unmounts the old tree (running effect cleanups and `componentWillUnmount`, detaching refs, discarding all `useState` and `useReducer` values), removes the DOM node, then mounts the new tree from scratch. The identical children below do not save anything — replacing a parent replaces everything under it, because React never searches for reusable pieces across a type change. Had the type stayed `div` and only a prop changed, React would have kept the same DOM node and patched just the changed attributes, preserving all state below.

code

jsx · 5 lines
jsx
export function Panel({ wide, children }) {
  return wide
    ? <section className="panel">{children}</section>
    : <div className="panel">{children}</div>;
}

go deeper

for a junior

Be ready to state the rule plainly: same element type at a position means update in place, a different type means the old subtree is thrown away and a new one built, with state lost.

for a middle

Explain the mechanics you can observe: which cleanups run, in what order the old tree unmounts before the new one mounts, and why a changed wrapper tag destroys state in children that did not change at all.

for a senior

Show you can use the rule as a diagnostic. When state resets in production, trace which render changed the type or the structure at that position, and argue whether the fix is stabilising the tree or accepting the remount.

for a principal

Frame it as an API-design constraint: component boundaries and conditional structure determine what survives an update, so a tree shape chosen carelessly builds state loss into the product. Set conventions that keep type-stable slots for stateful widgets.

## The comparison React makes first Every time a component renders, React produces a tree of plain element objects and walks it alongside the tree from the previous render, position by position. At each position the very first thing it compares is the element's `type`. For a host element the type is a string such as `'div'` or `'input'`; for a component it is a reference to the function (or class) itself. The outcome is binary: - **Types are equal** — React keeps the existing instance. For a host element that means the same DOM node stays in the document and only the props that actually changed are applied to it. For a component it means the same fiber, and therefore the same hook state, is reused; React calls the function again with the new props and recurses into what it returns. - **Types differ at all** — React stops comparing. It does not look inside to see whether anything is reusable. The old subtree is unmounted and a brand-new one is mounted in its place. ## What "the same position" means Position means the slot in the rendered tree: the same index under the same parent element. It is not the identity of the component function and not the line it was written on. Two elements written in different branches of a conditional can still land in the same position, and one element written on a single line can land in different positions on different renders. For a list of siblings, React matches by `key` rather than by index, which is a separate mechanism. ## What replacement actually costs A remount is not a repaint — it destroys everything the old tree was holding: - All `useState` / `useReducer` values in the subtree are discarded and re-initialised. - Every effect cleanup in the subtree runs, then the new tree's effects run as mounts. Subscriptions are torn down and re-established; timers restart. - Refs to the old DOM nodes are set to `null`; new nodes are created and refs re-attached. - The browser's own per-node state dies with the node: focus, text selection, scroll offset, the value of an uncontrolled input, the playback position of a `<video>`, an in-flight CSS transition, the document inside an `<iframe>`. - In legacy class components, `componentWillUnmount` runs on the old tree and `componentDidMount` on the new one. ## Why the check is total ```jsx // isWide flips -> the whole subtree below is thrown away return isWide ? <section className="panel"><Chat /></section> : <div className="panel"><Chat /></div>; ``` `Chat` is literally the same component in both branches, but it sits under a parent whose type changed, so it is unmounted with its parent and mounted fresh. React accepts this because the heuristic behind it holds most of the time: when the element type at a position changes, the developer usually meant something structurally different, and searching for salvageable fragments would cost more than rebuilding. ## The same rule for components ```jsx {mode === 'grid' ? <GridView items={items} /> : <ListView items={items} />} ``` `GridView` and `ListView` are different function references, so switching `mode` remounts. That is usually what you want — the two views have unrelated internal state. It becomes a bug when the two branches are really the *same* thing rendered slightly differently; then you keep one element and vary its props instead of swapping its type. ## The other side of the rule When the type is unchanged, the update is cheap and non-destructive. `<input className="a" />` becoming `<input className="b" />` sets one attribute on a node that never left the DOM, which is exactly why a controlled input does not lose focus while you type into it despite re-rendering on every keystroke. ## What to take away Type stability at a position is what preserves state. If a component's state resets unexpectedly, the first question is: what changed the element type or the tree structure at that position between the two renders?

  • If the wrapper element's type stays the same and only its className changes, what happens to the children?
    Nothing destructive. React keeps the same DOM node, sets the one changed attribute on it, and recurses into the children to compare them by the same rules. Component state, refs, focus and scroll below the wrapper all survive, because no instance in the subtree was replaced.
  • Does React compare the element types by value or by reference?
    Host elements are compared by their string type, so `'div'` matches `'div'`. Component elements are compared by the identity of the function or class itself, so two structurally identical component functions defined separately are different types and will not match.
  • Is a remount ever the behaviour you want?
    Yes — when a position genuinely represents a different thing, remounting gives you a clean slate: fresh state, fresh subscriptions, no leftovers from the previous occupant. Switching between two unrelated views at the same slot is the normal case, and letting them remount is simpler and safer than trying to reconcile their state.

saying these in an interview costs you the question

  • Thinks React diffs the real DOM against the new tree
  • Says identical children are reused across a parent type change
  • Believes state lives in the JSX, not in the mounted instance
  • Assumes React finds the minimal set of DOM edits globally
  • Claims a remount only recreates DOM, keeping hook state

context

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

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

React reconciles two element trees in roughly O(n) instead of using a general tree-diff algorithm, which costs about O(n^3). Which assumptions buy that speed, and what does React give up in exchange?

level: middleimportance: should knowfreq 42%

basics

~20 s

React assumes different element types produce different trees, and that keys identify stable children. Those bets replace an O(n^3) comparison with one O(n) pass, at the cost of never reusing a subtree that moved to a new parent.

open as a page

During a React re-render, under what conditions does React skip re-rendering a child subtree even though no React.memo is involved, and why does that almost never happen for a child written inline in the parent's JSX?

level: seniorimportance: should knowfreq 36%

basics

~20 s

React skips a child when its props object is referentially identical to the previous render, no update is pending on it, and no context it reads has changed. Inline JSX builds a new props object every render, so this rarely fires.

open as a page