What is reconciliation in React?
TL;DR
Reconciliation is React's render-phase process for matching a newly returned element tree with the previous tree. Element types, positions, and keys determine whether React preserves a component and its state or replaces it. Reconciliation calculates the required changes; the separate commit phase applies those changes to the DOM. A render can therefore occur without any DOM mutation.
What is reconciliation in React?
Reconciliation is React's process for matching a new element tree to the previous tree and determining what can be preserved before commit.
Introduction
Reconciliation is how React matches a new tree of element descriptions with the previous tree. It determines which component identities and state to preserve and which host changes may be needed. The later commit phase applies those changes to the DOM.
The virtual DOM
React maintains a virtual DOM to optimize updates. The virtual DOM isn't a copy of the real DOM — it's a tree of plain JavaScript objects describing what the UI should look like (essentially the return values of your components). When a component renders, React produces a new tree of these element descriptions and compares it against the previous tree to figure out what changed.
The diffing algorithm
The process of comparing the new tree with the previous one is called "diffing." React uses an O(n) heuristic algorithm based on two assumptions: elements of different types produce different trees, and the developer can hint at stable identity for list children using key. The algorithm works as follows:
- Element type comparison: If the elements at the same position are of different types (for example a
<div>becomes a<span>, or<ComponentA />becomes<ComponentB />), React unmounts the old subtree and mounts a fresh one — state is thrown away. - Same type, different props: If the element type is the same, React keeps the same DOM node (or component instance) and updates only the props that changed. Component state is preserved across the update because state is owned by the component instance, not derived by diffing.
- List children and keys: When rendering arrays of children, React uses the
keyprop to match items between renders. Stable keys let React move, insert, and delete list items efficiently; without them (or with index keys on a reordering list), React may unmount and remount items unnecessarily, losing their state.
Type and key determine whether reconciliation preserves an existing identity or replaces it:
Fiber, lanes, and bailouts
Since React 16, reconciliation runs on the Fiber architecture, which splits the work into small units that can be paused, resumed, and prioritized. React 18+ assigns updates to "lanes" so urgent work (like input) can interrupt non-urgent work (like a startTransition-wrapped update).
React also performs several bailouts to skip unnecessary work:
- If a component re-renders but its output is referentially equal to the previous render in the relevant ways, React can skip updating its children.
memowraps a component so React can skip re-rendering it when every prop compares equal to its previous value withObject.is(unless a custom comparator says otherwise).useMemoanduseCallbackpreserve referential equality for derived values and callbacks.- React Compiler 1.0, when enabled, applies memoization to compatible code at build time and can reduce the need for hand-written
memo,useMemo, anduseCallback. It is optional and may skip code it cannot safely optimize.
Updating the DOM
Once the diffing phase ("render phase") has identified the changes, React enters the "commit phase" and applies them to the real DOM in a single synchronous pass. This split is what allows the render phase to be interruptible while the commit stays atomic.
Example
Here is a simple example to illustrate reconciliation. Note the use of the functional updater setCount(c => c + 1) — passing a function avoids the stale-state bug you'd hit by reading count directly inside the handler.
import { useState } from 'react';function MyComponent() {const [count, setCount] = useState(0);function increment() {setCount((c) => c + 1);}return (<div><p>{count}</p><button onClick={increment}>Increment</button></div>);}
When increment runs, React schedules a re-render. It calls MyComponent again, gets a new element tree, and diffs it against the previous tree. Everything matches by type, so React keeps the existing DOM nodes and updates only the text inside <p>.
Further reading
- React documentation on preserving and resetting state
- Render and commit
- React Compiler
- React Fiber Architecture