React Reconciliation Algorithm
Answer
Reconciliation is the process React uses to compare the Virtual DOM trees and determine the minimum number of changes needed to update the real DOM.
Reconciliation Flow
Key Assumptions
React's diffing algorithm makes two assumptions:
- Different element types produce different trees
- Developer can hint stable elements with
keyprop
Element Type Changes
// Different types = completely new tree
// Old:
<div>
<Counter />
</div>
// New:
<span>
<Counter />
</span>
// React destroys entire <div> subtree and rebuilds <span>
// Counter component is unmounted and remounted (loses state!)
Same Element Type
// Same type = update attributes only
// Old:
<div className="old" title="old" />
// New:
<div className="new" title="old" />
// React only updates className, keeps same DOM node
Component Updates
// Same component type = instance preserved
// Old:
<Counter count={1} />
// New:
<Counter count={2} />
// Same Counter instance, just receives new props
// State is preserved, componentDidUpdate/useEffect runs
The Key Prop
// ❌ Without keys - inefficient updates
{
items.map((item) => <ListItem item={item} />);
}
// ✅ With stable keys - efficient updates
{
items.map((item) => <ListItem key={item.id} item={item} />);
}
// ❌ Index as key - broken for reorderable lists
{
items.map((item, index) => <ListItem key={index} item={item} />);
}
// If items reorder, keys stay same but content shifts
When Index Keys Break
// Problem: Controlled inputs lose their values
function TodoList({ todos }) {
return (
<ul>
{todos.map((todo, index) => (
// ❌ Using index as key
<li key={index}>
<input defaultValue={todo.text} />
</li>
))}
</ul>
);
}
// If todo is deleted from middle:
// - Remaining todos get wrong keys
// - Input values appear in wrong items
// - Bug!
Reconciliation Example
// State: [A, B, C] → [D, A, B, C]
// Without keys (by index):
// 0: A → D (update A to D)
// 1: B → A (update B to A)
// 2: C → B (update C to B)
// 3: undefined → C (create C)
// = 4 operations
// With keys:
// key=A: keep A (move to index 1)
// key=B: keep B (move to index 2)
// key=C: keep C (move to index 3)
// key=D: create D (insert at index 0)
// = 1 create, 3 reorders (more efficient for components with state)
Performance Implications
// Heavy component with keys - React can reuse instances
const MemoizedComponent = React.memo(({ data }) => {
// Expensive initialization
const processed = heavyProcessing(data);
return <div>{processed}</div>;
});
function List({ items }) {
return items.map((item) => (
<MemoizedComponent
key={item.id} // Stable key
data={item}
/>
));
}
// Items reorder: components move, don't reinitialize
Children Reconciliation
// Updating children lists
// Old: A, B
// New: A, B, C
// React diffs from start:
// A === A ✓ keep
// B === B ✓ keep
// C = new, add at end ✓ efficient
// New: Z, A, B
// Z !== A ✗ React mutates A→Z, B→A, adds B
// This is why keys matter for insertions at beginning!
Key Points
- Reconciliation diffs old and new VDOM trees
- Same element type = update attributes
- Different element type = replace entire subtree
- Keys help React identify which items changed
- Use stable IDs as keys, not array indices
- Keys must be unique among siblings
- Proper keys improve reorder/insert performance