Virtual DOM Diffing Demystified: What React Compares and Why It Pays
Understand what React's Virtual DOM really is, how reconciliation diffs two element trees, what the commit phase changes, and where the performance benefit comes from.
This article is published in English.
Interfaces change constantly: a notification appears, a button toggles, a counter ticks, a list re-renders. Every such change must reach the browser DOM, and updating the DOM carelessly is a reliable way to make a page feel slow. React's answer is the Virtual DOM plus a diffing step called reconciliation. This article explains what React keeps in memory, how it decides what changed, and why that helps, so you can reason about updates with an accurate mental model.
Why writing to the DOM directly gets expensive
Before React, UI updates were imperative: find a node, change it.
document.getElementById("title").innerText = "Hello";
That is fine on a small page. When hundreds or thousands of elements change often, it becomes both slow and hard to manage, because a DOM change can force the browser to redo:
- style and layout calculation
- reflow of surrounding content
- repainting of affected pixels
- re-rendering of parts of the page
Each time, the browser recomputes where elements sit, how large they are and which pixels to redraw. That work is far more expensive than creating or comparing JavaScript objects. On top of the cost, many pieces of code reaching into the DOM independently make it easy for the screen to drift out of sync with application state.
What the Virtual DOM actually is
The real DOM
The real DOM is the live node tree the browser builds from markup, one node per element. Even this:
<body>
<h1>Hello</h1>
</body>
becomes an internal tree tied to layout and paint, and mutating it frequently is what triggers the costly work above.
The virtual representation
The Virtual DOM is often called a lightweight copy of the real DOM. More precisely, it is a description of what the UI should look like: plain objects your components return when they render. React updates this description first and touches the browser later. One element looks roughly like this:
{
type: "h1",
props: {
children: "Hello"
}
}
Such objects are cheap to create and compare, so React can work out what changed before performing any real DOM operation. JSX is just syntax for producing them.
From first render to a changed tree
On the first render of a component like this:
function App() {
return <h1>Hello World</h1>;
}
React builds the virtual tree, creates matching DOM nodes and inserts them into the page. The two trees now agree, and the UI is visible.
Later, a click handler might update state:
setCount(count + 1);
or a parent passes new props. That signals that part of the UI may need to change. React does not patch the DOM immediately; it schedules a new render, and updates from the same event are usually batched together.
During that render, React calls the component again and gets a fresh tree, so it now holds the previous version and the new one. The old tree:
<h1>Hello</h1>
The new tree:
<h1>Hello Omer</h1>
The job is to determine exactly what differs.
Reconciliation: how the diff works
Reconciliation means comparing the previous virtual tree with the new one; the comparison itself is usually called diffing. For each position, React asks whether an element changed, stayed the same, or was added or removed. Conceptually:
Old Tree VS New Tree
The aim is the smallest set of changes that turns the old UI into the new one. A fully general tree diff would be too slow, so React uses two heuristics. If an element's type changes, for example div to span, React replaces that subtree instead of matching its children. In lists, it matches children by key, which is why stable keys matter: they tell React an item moved rather than being replaced.
When only the text changes
Before:
<h1>Hello</h1>
After:
<h1>Hello Omer</h1>
The <h1> has the same type in the same place; only its text differs. React therefore keeps the existing DOM node and updates just the text node instead of recreating the element. Skipping every operation that is not strictly needed is the core of the optimization.
The commit phase: applying only what changed
After diffing, React writes the resulting changes to the real DOM:
- updating text
- setting or removing attributes
- inserting new elements
- deleting elements that disappeared
This is the commit phase, the only point where React touches the DOM, and it changes only what the diff flagged. The browser then repaints the affected areas. For the path from commit to pixels in more depth, see our guide to React rendering from state updates to screen pixels.
Where the performance benefit comes from
The gain comes from fewer expensive DOM operations. Instead of rebuilding everything, React compares the old and new descriptions, computes the minimal differences and touches only the affected nodes. Since object comparison is much cheaper than DOM mutation, this usually pays off in large applications.
Two caveats keep the model honest. The Virtual DOM is not inherently faster than carefully hand-tuned DOM code; its value is letting you write declarative UI while still getting efficient updates. And rendering is not free: React still calls your components and diffs their output, so avoiding unnecessary re-renders still matters.
Key takeaways
- The Virtual DOM is a set of plain objects describing the intended UI, recreated on each render.
- Reconciliation diffs old and new trees using element type and
keyas shortcuts. - The commit phase is the only place React writes to the DOM, and it writes only differences.
- You trade some render and diff work for far fewer costly DOM mutations and a simpler programming model.