13_React Reconciliation & the Diffing Algorithm - Maniconserve/React-Wiki GitHub Wiki
Updating the real browser DOM is slow. Every time you change something — a counter, a name, a list — the browser has to repaint and reflow the page. If you update everything on every change, even the parts that didn't change, it becomes very slow for large UIs.
In plain HTML and JavaScript, when you update the UI, you manually find the element and change it. The browser has no idea what changed — it just does what you tell it.
<!-- index.html -->
<div id="app">
<h1 id="title">Hello</h1>
<p id="count">Count: 0</p>
<button onclick="increment()">Add</button>
</div>
// app.js
let count = 0;
function increment() {
count++;
// manually find and update ONLY the count element
document.getElementById('count').textContent = 'Count: ' + count;
}
This works, but you are responsible for tracking exactly which elements to update. In large applications with hundreds of elements, this becomes very hard to manage and error-prone.
React introduces a Virtual DOM — a lightweight JavaScript copy of the real DOM that lives in memory.
When state changes in React:
- React creates a new Virtual DOM tree representing what the UI should look like
- React compares the new tree with the previous tree (this is called diffing)
- React figures out the minimum number of changes needed
- React updates only those changed parts in the real DOM
This comparison and update process is called Reconciliation.
State changes
│
▼
New Virtual DOM created
│
▼
Diffing — compare new vs old Virtual DOM
│
▼
Find the minimum changes needed
│
▼
Update only those parts in the real DOM
import { useState } from 'react';
function App() {
const [count, setCount] = useState(0);
return (
<div>
<h1>Hello</h1> {/* never changes /}
<p>Count: {count}</p> {/ changes on every click */}
<button onClick={() => setCount(count + 1)}>Add</button>
</div>
);
}
When the button is clicked:
- React creates a new Virtual DOM
- Compares it to the previous one
- Sees that
<h1>Hello</h1>is identical — skips it - Sees that
<p>Count: 1</p>is different — updates only that element in the real DOM -
<button>is also identical — skipped
The <h1> and <button> are never touched in the real DOM even though setCount triggers a re-render.
React's diffing algorithm follows two main rules to keep comparisons fast.
If the element type changes (e.g., <div> becomes <span>), React tears down the old tree and builds a completely new one from scratch — including all children.
// Before
<div>
<p>Hello</p>
</div>
// After — type changed from div to span
<span>
<p>Hello</p>
</span>
React does not try to reuse the <div>. It destroys it and creates a new <span> with its children.
If the element type is the same, React keeps the existing DOM node and only updates what changed.
// Before
<p className="normal" style={{ color: 'black' }}>Hello</p>
// After
<p className="highlight" style={{ color: 'red' }}>Hello</p>
React sees it is still a <p> — so it only updates className and color. The element itself stays in the DOM.
When rendering lists, React needs a way to identify which item is which between renders. Without key, React compares items by position — which causes bugs when items are reordered or removed.
// Before
<ul>
<li>Apple</li> {/* position 0 */}
<li>Banana</li> {/* position 1 */}
<li>Mango</li> {/* position 2 */}
</ul>
// After — "Apple" removed
<ul>
<li>Banana</li> {/* now at position 0 — React thinks this changed /}
<li>Mango</li> {/ now at position 1 — React thinks this changed too */}
</ul>
React sees that positions 0 and 1 "changed" and re-renders both — even though only Apple was removed. This is wasteful and can also cause incorrect UI in complex components.
const fruits = [
{ id: 1, name: 'Apple' },
{ id: 2, name: 'Banana' },
{ id: 3, name: 'Mango' },
];
<ul>
{fruits.map(fruit => (
<li key={fruit.id}>{fruit.name}</li>
))}
</ul>
Now when Apple is removed, React knows exactly which element to remove using the key. Banana and Mango are untouched.
Always use a stable unique ID from your data as the key. Using
indexas a key is fine for static lists but causes bugs if the list is reordered or filtered.
| Real DOM | Virtual DOM | |
|---|---|---|
| What it is | Actual browser elements | JavaScript object in memory |
| Speed | Slow to update | Fast to compare |
| Who updates it | You (in plain JS) or React (after diffing) | React (on every state change) |
| What gets updated | Whatever you tell it | Only what actually changed |
PLAIN HTML + JS
──────────────────────────────────────
User clicks button
│
▼
Your code runs document.getElementById(...)
│
▼
Browser updates that specific element
(you decide what to update — manual work)
REACT
──────────────────────────────────────
User clicks button → setCount called
│
▼
React creates new Virtual DOM
│
▼
Diffing — compares new vs old Virtual DOM
│
▼
Only changed nodes updated in real DOM
(React decides what to update — automatic)
- The Virtual DOM is a fast, in-memory copy of the real DOM
- Reconciliation is the process of syncing the Virtual DOM changes to the real DOM
- The diffing algorithm compares old and new Virtual DOM trees to find the minimum changes needed
- Different element types cause a full rebuild; same types cause partial updates
- The
keyprop helps React correctly identify list items so it only updates what actually changed