
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/useMemofor 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.









