Skip to content

Reconciliation Process — Complete Guide

DodaTech Updated 2026-06-28 5 min read

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

  1. Assuming reconciliation only compares the changed component (it recurses through the entire subtree).
  2. Using random keys like Math.random() or Date.now() which force full re-creation every render.
  3. Mutating state before reconciliation, causing incorrect diff results.
  4. Forgetting that reconciliation builds a new tree every render — object references change.
  5. Expecting interrupted renders to produce partial DOM updates (they produce no updates until committed).

Practice Questions

  1. What is reconciliation? The process of comparing virtual DOM trees and generating minimal real DOM updates.
  2. What are the two phases of reconciliation? Render phase (build virtual tree, diff) and commit phase (apply patches).
  3. How do keys improve reconciliation? They allow matching children across renders by identity, not position.
  4. 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

What is reconciliation in Virtual DOM?

Reconciliation is the algorithm frameworks use to compare the new virtual tree with the old one and compute the minimal set of DOM operations needed.

Is reconciliation the same as diffing?

Diffing is part of reconciliation. Reconciliation includes the full lifecycle: render, diff, patch, and commit.

Can reconciliation be interrupted?

In React Fiber, the render phase can be interrupted. The commit phase is always synchronous.

How do keys affect reconciliation?

Keys allow the reconciler to identify items across renders, enabling efficient moves and avoiding unnecessary re-creation.

What happens during the commit phase?

All computed DOM mutations are applied synchronously. The commit phase must complete without interruption.

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