๐Ÿ“š Learning Hub
ยท 3 min read

How React's Virtual DOM Actually Works


Every React developer knows โ€œReact uses a virtual DOM.โ€ Few can explain what that actually means. Hereโ€™s the full picture.

The problem: DOM updates are slow

The browserโ€™s DOM (Document Object Model) is a tree of objects representing your HTML. Updating it is expensive because every change can trigger:

  • Style recalculation
  • Layout (reflow)
  • Paint
  • Compositing

Changing one elementโ€™s text? Fast. Changing 100 elements? The browser might reflow the entire page.

Reactโ€™s solution: diff first, update minimally

Instead of updating the DOM directly, React:

  1. Keeps a lightweight copy of the DOM in memory (the virtual DOM)
  2. When state changes, builds a new virtual DOM tree
  3. Compares the new tree with the old tree (diffing)
  4. Calculates the minimum set of real DOM changes needed
  5. Applies those changes in a batch

What the virtual DOM looks like

Itโ€™s just JavaScript objects:

// This JSX:
<div className="card">
  <h1>Hello</h1>
  <p>World</p>
</div>

// Becomes this virtual DOM object:
{
  type: 'div',
  props: { className: 'card' },
  children: [
    { type: 'h1', props: {}, children: ['Hello'] },
    { type: 'p', props: {}, children: ['World'] }
  ]
}

Creating and comparing JavaScript objects is orders of magnitude faster than touching the real DOM.

The diffing algorithm (reconciliation)

Reactโ€™s diff algorithm has three rules that make it O(n) instead of O(nยณ):

Rule 1: Different types = full replace

If an element changes type (e.g., <div> to <span>), React destroys the old tree and builds a new one. No attempt to reuse.

// Before:
<div><Counter /></div>

// After:
<span><Counter /></span>

// React: destroy everything inside <div>, rebuild inside <span>
// Counter's state is lost

Rule 2: Same type = update props

If the type is the same, React keeps the DOM node and only updates changed attributes.

// Before:
<div className="old" style={{color: 'red'}} />

// After:
<div className="new" style={{color: 'blue'}} />

// React: only updates className and color. One DOM operation.

Rule 3: Keys identify list items

When diffing lists, React uses key props to match old and new items:

// Before:
<li key="a">Alice</li>
<li key="b">Bob</li>

// After:
<li key="b">Bob</li>
<li key="a">Alice</li>

// With keys: React knows to reorder, not recreate
// Without keys: React updates both elements' text content

This is why missing keys cause bugs and performance issues.

The update cycle

setState() called
    โ†“
New virtual DOM tree created
    โ†“
Diff old tree vs new tree
    โ†“
List of minimal changes (patches)
    โ†“
Batch apply to real DOM
    โ†“
Browser repaints

React batches multiple setState calls into a single update cycle. If you call setState three times in an event handler, React diffs once and applies once โ€” not three times.

React 18: Concurrent rendering

React 18 added the ability to pause and resume rendering. If a high-priority update (user typing) comes in while a low-priority update (loading data) is rendering, React can:

  1. Pause the low-priority render
  2. Handle the high-priority update
  3. Resume or restart the low-priority render

The virtual DOM makes this possible because itโ€™s just JavaScript objects โ€” you can throw away a half-built tree and start over with no DOM side effects.

Is the virtual DOM actually faster?

Controversial take: the virtual DOM isnโ€™t faster than hand-optimized DOM updates. A developer who knows exactly which element to update can do it in one line of vanilla JS.

The virtual DOM is faster than naive DOM updates. Itโ€™s a trade-off: you write declarative code (โ€œhereโ€™s what the UI should look likeโ€) and React figures out the minimal DOM changes. You sacrifice some raw performance for developer productivity.

Frameworks like Svelte and SolidJS skip the virtual DOM entirely and compile to direct DOM updates. They can be faster for certain workloads. But Reactโ€™s approach is โ€œfast enoughโ€ for 99% of applications.

Related: React Interview Questions

๐Ÿ“˜