Reconciliation Process — Complete Guide
In this tutorial, you will learn about Reconciliation Process. We cover key concepts, practical examples, and best practices to help you master this topic.
Reconciliation is the process frameworks use to compare Virtual Dom trees and apply minimal updates to real DOM, balancing correctness with performance.
What You'll Learn
- What reconciliation is and how it differs from simple diffing
- The reconciliation lifecycle from state change to DOM update
- How React's reconciliation algorithm works
- How Vue's reconciliation differs from React's
Why It Matters
Reconciliation is the bridge between application state and the visual UI. Understanding it helps you debug performance issues, optimize component structure, and choose the right framework for your needs.
flowchart LR
A[State Change] --> B[Trigger re-render]
B --> C[Build new virtual tree]
C --> D[Reconcile with old tree]
D --> E{Has keys?}
E -->|Yes| F[Keyed reconciliation]
E -->|No| G[Positional reconciliation]
F --> H[Generate minimal patches]
G --> H
H --> I[Apply to real DOM]
I --> J[Commit phase]
The Reconciliation Lifecycle
Reconciliation follows a lifecycle: trigger, render, diff, commit.
class SimpleFramework {
constructor(rootComponent, container) {
this._rootComponent = rootComponent;
this._container = container;
this._oldVTree = null;
this._pendingState = null;
this._isDirty = false;
}
// Called when state changes
setState(updater) {
// 1. Schedule update (batching)
this._pendingState = updater;
if (!this._isDirty) {
this._isDirty = true;
// Use microtask for batching
Promise.resolve().then(() => this._reconcile());
}
}
_reconcile() {
// 2. Render phase: build new virtual tree
const newVTree = this._rootComponent.render();
// 3. Diff phase: compare with old tree
if (this._oldVTree) {
this._patches = this._diff(this._oldVTree, newVTree);
}
// 4. Commit phase: apply patches to real DOM
if (this._patches) {
this._commit(this._container, this._patches);
}
this._oldVTree = newVTree;
this._isDirty = false;
}
_diff(oldNode, newNode) {
// Simplified diff logic
if (!oldNode) return { type: 'CREATE', node: newNode };
if (!newNode) return { type: 'DELETE' };
if (oldNode.type !== newNode.type) return { type: 'REPLACE', node: newNode };
// ... prop and child diffing
}
_commit(parent, patches) {
// Apply patches to the real DOM
// This is called the "commit phase"
}
}
Render Phase vs Commit Phase
The render phase can be interrupted. The commit phase is synchronous and cannot be interrupted (in React Fiber).
// Render phase (can be interrupted)
function renderPhase(component) {
// Build virtual tree
const vnode = component.render();
// Diff with previous
const patches = diff(previousVnode, vnode);
// If the render is interrupted (e.g., higher priority update),
// the patches are discarded and reconciliation restarts
return patches;
}
// Commit phase (synchronous, cannot be interrupted)
function commitPhase(domNode, patches) {
// All DOM mutations happen here
// This must complete without interruption
patches.forEach(patch => {
applyPatch(domNode, patch);
});
// After commit, the browser sees the final DOM state
}
Keyed Reconciliation
Keys allow the reconciler to match children across renders even when their order changes.
// Without keys: positional matching
// Old: [A, B, C, D]
// New: [B, C, D, E]
// Positional diff would replace A->B, B->C, C->D, D->E
// Result: 4 mutations
// With keys:
// Old: [{key: 'a'}, {key: 'b'}, {key: 'c'}, {key: 'd'}]
// New: [{key: 'b'}, {key: 'c'}, {key: 'd'}, {key: 'e'}]
// Keyed diff matches b->b, c->c, d->d, removes a, adds e
// Result: 2 mutations (remove a, add e)
function keyedReconciliation(oldChildren, newChildren) {
const oldMap = new Map();
oldChildren.forEach((child, index) => {
oldMap.set(child.key, { node: child, index });
});
const operations = [];
let lastPlacedIndex = -1;
newChildren.forEach((newChild, newIndex) => {
const oldEntry = oldMap.get(newChild.key);
if (!oldEntry) {
// New item
operations.push({ type: 'ADD', node: newChild, index: newIndex });
} else {
// Existing item
if (oldEntry.index < lastPlacedIndex) {
// Item moved down — need to move it
operations.push({ type: 'MOVE', node: oldEntry.node, from: oldEntry.index, to: newIndex });
}
lastPlacedIndex = Math.max(lastPlacedIndex, oldEntry.index);
oldMap.delete(newChild.key);
}
});
// Remaining items in oldMap are deleted
oldMap.forEach((entry) => {
operations.push({ type: 'REMOVE', node: entry.node, index: entry.index });
});
return operations;
}
Recursive Reconciliation
Reconciliation recurses into child components, not just DOM elements.
function reconcileComponent(parentComponent, newState) {
// 1. Update component state
parentComponent.state = { ...parentComponent.state, ...newState };
// 2. Recurse into children
const children = parentComponent.renderChildren();
children.forEach((child, index) => {
const oldChild = parentComponent._previousChildren[index];
if (child.type === 'component') {
// Recursively reconcile child components
reconcileComponent(
oldChild?.component || createChildComponent(child),
child.props
);
} else {
// Reconcile DOM elements directly
reconcileDOM(oldChild, child);
}
});
// 3. Generate patches for this level
return generatePatches(parentComponent);
}
// Each component manages its own reconciliation loop.
// This creates a tree of reconciliation operations.
Common Mistakes
- Assuming reconciliation only compares the changed component (it recurses through the entire subtree).
- Using random keys like Math.random() or Date.now() which force full re-creation every render.
- Mutating state before reconciliation, causing incorrect diff results.
- Forgetting that reconciliation builds a new tree every render — object references change.
- Expecting interrupted renders to produce partial DOM updates (they produce no updates until committed).
Practice Questions
- What is reconciliation? The process of comparing virtual DOM trees and generating minimal real DOM updates.
- What are the two phases of reconciliation? Render phase (build virtual tree, diff) and commit phase (apply patches).
- How do keys improve reconciliation? They allow matching children across renders by identity, not position.
- Can the render phase be interrupted? Yes, in modern frameworks like React with Fiber architecture.
Challenge
Build a reconciliation visualizer that shows step by step how a list of items is reconciled when items are added, removed, or reordered. Display the old tree, new tree, and the generated patches (add, remove, move, update).
FAQ
Mini Project
Build a visual reconciliation debugger. Create a component that renders a list of items. Add buttons to add, remove, shuffle, and update items. Show a live display of the old tree, new tree, and the patches generated during reconciliation.
What's Next
Lesson 4: Keys in Virtual DOM
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro