How does the virtual DOM work?
By FrontendPro Editorial Team Updated 9/26/2026
Answer
The virtual DOM is a tree of plain JavaScript objects describing what the UI should look like. When state changes React builds a new tree, compares it with the previous one, and applies only the differences to the real DOM.
// what <div className="a">Hi</div> actually is
{ type: 'div', props: { className: 'a', children: 'Hi' } }
The three steps
- Render — your components run and return a new tree of these objects. Nothing touches the browser.
- Reconcile (diff) — React walks old and new together and records what changed.
- Commit — React applies that list of changes to the real DOM in one synchronous batch.
The diffing rules that matter
React does not do a general tree diff, which would be O(n³). It uses two heuristics to get O(n):
- Different type at the same position means throw the subtree away. A
<div>replaced by a<span>unmounts everything inside it and its state. This is why conditionally swapping a wrapper element loses child state. - Children are matched by
key. With stable keys, React knows an item moved rather than changed. Without them it compares by position, so inserting at the front makes every subsequent item look different.
What the virtual DOM is actually for
Not raw speed. Hand-written DOM updates, done perfectly, are faster — there is no diff to pay for. The virtual DOM buys you two other things:
- A declarative programming model. You describe the target state; React works out the transitions. You never write "if the list had 3 items and now has 4, insert a node at index 2."
- Batched, minimal writes. Ten state updates in one handler produce one DOM commit. Layout thrashing from interleaved reads and writes mostly disappears.
And because the tree is just objects, it enables things a DOM-bound library cannot do: rendering to native (React Native), to a canvas, or to a string on the server, and — since React 18 — interrupting a render in progress to handle urgent input first. Concurrent rendering is only possible because the work happens off-DOM.
The downsides, honestly
- Memory and CPU overhead for the tree and the diff on every update. Usually negligible, occasionally not.
- It hides the cost of over-rendering. A component re-rendering fifty times a second still diffs fifty times, and the code looks fine.
- Bundle size — React itself, before a line of your code.
- A ceiling on fine-grained updates. Signal-based frameworks (Svelte 5, Solid, Vue's reactivity) update the exact DOM node that depends on a changed value, with no diff at all, and beat React on that benchmark.
Where React is heading
The React Compiler memoises automatically, which removes most manual useMemo/memo work, and cuts down how much gets diffed in the first place. The virtual DOM is not going away; the amount of it that runs per update is shrinking.
