DOM Reconciliation and Diffing

How to compare archived Virtual DOM arrays with new ones to make incisive, performant edits to the real DOM.

April 20, 20264 min read2 / 2

We have finally achieved a declarative UI. Our entire interface is generated from a single description (our Virtual DOM array), and that generation only triggers when data changes (via our state hooks).

However, the cost of generating that UI is astronomical. To keep our code simple, we are using document.body.replaceChildren(...elems) inside our updateDOM function. This entirely destroys the real C++ DOM nodes and rebuilds them. If a user types the letter "F", the entire application is thrown away and rebuilt just to add the "F" to the screen.

The Promise of the Virtual DOM

This is the exact problem the Virtual DOM was invented to solve.

Instead of working with slow, heavy real DOM nodes in C++, our createVDOM function generates plain JavaScript arrays. Arrays are incredibly fast to generate and cheap to store in memory.

Because they are cheap to store, we don't have to throw them away. When the state changes, we can generate our new Virtual DOM array, but keep the previous Virtual DOM array archived in memory.

JavaScript
let prevVDOM = []; let vDOM = []; function updateDOM() { // Archive the old representation prevVDOM = [...vDOM]; // Generate the new representation vDOM = createVDOM(); // Instead of replacing children, find the diff! findDiff(prevVDOM, vDOM); }

By diffing (comparing) the old array against the new array, we can figure out exactly what changed.

Writing a Diffing Algorithm

Let's imagine our data just changed from "" to "F".

Our archived prevVDOM array looks like this: [["input", "", handle], ["div", "Hello "], ["div", "Great job"]]

Our new vDOM array looks like this: [["input", "F", handle], ["div", "Hello "], ["div", "Great job"]]

Since our arrays align index-to-index, we can write a findDiff loop to iterate through them and compare them.

JavaScript
function findDiff(prev, current) { for (let i = 0; i < current.length; i++) { // A quick way to compare deep javascript arrays if (JSON.stringify(prev[i]) !== JSON.stringify(current[i])) { // If they are different, make an incisive edit! let newValue = current[i][1]; if (current[i][0] === "input") { elems[i].value = newValue; } else { elems[i].textContent = newValue; } } } }

Let's trace this:

  1. Index 0: It compares prevVDOM[0] vs vDOM[0]. "" is not "F". Our if block triggers. It reaches into our previously stored real DOM accessor (elems[0]) and precisely updates its value to "F".
  2. Index 1: It compares the first div. They are identical. The algorithm skips them entirely. Zero C++ UI updates happen.
  3. Index 2: It compares the second div. They are identical. It skips them.

The diffing algorithm comparing archived prevVDOM against the new vDOM to bypass DOM generation ExpandThe diffing algorithm comparing archived prevVDOM against the new vDOM to bypass DOM generation

Reconciliation

This process of comparing Virtual DOM architectures and surgically updating the real DOM is called Reconciliation.

Every time you type a key in a React application, React builds an entirely new Virtual DOM tree of your component, compares it to the previous tree, and batches the absolute minimum number of DOM edits required to make the screen match your new data state.

All those CPU cycles we just saved by not destroying our divs? That is why you can have 10,000 rows in a React table and still have the application feel instantly responsive when you type in a search box.

The UI Engineering Cycle

We've reached the end of our journey rebuilding a modern UI framework from scratch. Look at what we achieved:

  1. The DOM: We started in C++, recognizing the limitations of imperative C++ accessor objects.
  2. Event Target: We discovered how the browser handles events and linked objects tightly via event handlers.
  3. Vanilla JS Binding: We struggled through manual, brittle 2-way data binding.
  4. The Virtual DOM: We invented a JSON-like structure to describe our UI visually in Javascript.
  5. Components: We abstracted repetitive arrays into reusable functions called Components.
  6. Reconciliation: We used State Hooks to trigger diffing loops, saving CPU cycles and isolating edits to precisely where the data changed.

You now understand the hard parts of UI development. Every modern framework--whether it's React, Vue, Svelte, or Solid--is simply traversing this identical path, playing differing games of trade-offs between Virtual DOM sizes, compilation steps, or signal-based reactivity.

The implementations vary, but the paradigm is permanent.


Further Reading and Watching

Practice what you just read.

DOM Reconciliation and Diffing
1 exercise