6 min read
0%

Virtual DOM Diffing

Back to Blog
Virtual DOM Diffing

Virtual DOM Diffing

The virtual DOM is a JavaScript object tree that mirrors the real DOM. Diffing is the process of comparing two snapshots of it to compute the minimal set of DOM mutations. Here’s what that actually means in practice.

What the Virtual DOM Is

React elements aren’t DOM nodes — they’re plain objects:

// JSX compiles to:
React.createElement('div', { className: 'card' }, 'Hello')

// Which produces:
{
  type: 'div',
  props: { className: 'card', children: 'Hello' },
  key: null,
  ref: null,
}

A component tree is a tree of these objects. React holds two copies: the current tree (what’s on screen) and the work-in-progress tree (what the next render produced).

Why Not Just Re-render the DOM

Direct DOM mutation is not inherently slow. The problem is doing more of it than necessary. The virtual DOM lets React batch and minimize writes:

// Without vdom: every state change → full DOM rebuild
// With vdom: React diffs old and new → only changed attrs/nodes updated

In practice, the virtual DOM overhead (object creation + diffing) is worthwhile when most of the tree is stable. For highly dynamic UIs (e.g., canvas, large tables), other approaches sometimes win.

The Diffing Algorithm

React walks both trees simultaneously, node by node:

Old tree:         New tree:
<ul>              <ul>
  <li>A</li>        <li>A</li>
  <li>B</li>        <li>C</li>  ← changed
</ul>             </ul>

At position 1, old is <li>B</li> and new is <li>C</li>. Same type (li), so React patches the text content. One DOM write.

When types differ:

Old: <p>text</p>
New: <span>text</span>

React unmounts <p> and mounts <span> — even though the content is identical. Different type = full subtree replacement.

Fiber Nodes

Each virtual DOM element corresponds to a Fiber node — a richer object that tracks:

{
  type,          // element type (string or function)
  key,           // reconciliation hint
  stateNode,     // actual DOM node or class instance
  child,         // first child fiber
  sibling,       // next sibling fiber
  return,        // parent fiber
  pendingProps,  // new props from render
  memoizedProps, // props after last commit
  memoizedState, // hook state linked list
  flags,         // what work needs doing (Update, Placement, Deletion)
  alternate,     // pointer to other tree (current ↔ work-in-progress)
}

The flags bitmask is how React accumulates mutations during the diff phase and applies them all in one commit phase pass.

Batching Mutations

React doesn’t apply DOM mutations as it diffs. It collects effects into an effect list, then flushes them together in the commit phase:

Render phase  → build work-in-progress tree, compute flags
Commit phase  → walk effect list, mutate DOM, run effects

This means the browser sees one coherent update rather than partial states.

Keys and the Diffing Shortcut

Without keys, React compares lists by position. With keys, it builds a map:

// React builds: { 'id-1': fiberA, 'id-2': fiberB, 'id-3': fiberC }
// Then matches new elements against it by key

This lets React reuse existing fiber nodes (and their DOM nodes) even when list order changes, avoiding unnecessary unmount/remount cycles.

What the vDOM Does Not Guarantee

  • It doesn’t prevent unnecessary re-renders (use React.memo / useMemo for that)
  • It doesn’t eliminate DOM writes for unchanged nodes that happen to re-render
  • It’s not always faster than direct DOM manipulation — it’s a trade-off for developer ergonomics at reasonable performance

The vdom’s real value is a declarative programming model. You describe what the UI should look like; React figures out the mutations.


Canvas is not supported in your browser