Vue 3 Virtual DOM & Diffing Algorithm
Traditionally, Virtual DOM diffing (pioneered by React) is runtime-only and requires recursively traversing and comparing the entire old and new virtual trees. As applications scale, this creates significant CPU overhead. Vue 3 solves this by integrating compile-time optimizations with runtime diffing, creating a "compiler-informed" Virtual DOM.
1. What the Interviewer Is Testing
Interviewers ask this question to evaluate your deep understanding of component rendering performance:
- Traditional Diffing Limitations: Why is pure Virtual DOM diffing CPU-heavy?
- Compile-time Analysis: How does the Vue compiler flag dynamic vs static sections in a template?
- Patch Flags: What are bitwise flags and how do they speed up property comparisons?
- Static Hoisting & Block Trees: How does Vue skip checking static nodes entirely?
2. Compile-Time Optimizations in Vue 3
Instead of doing blind diffing at runtime, the Vue 3 compiler analyzes the template text before compilation and generates hints that the runtime renderer uses to skip redundant checks.
1. Patch Flags (Dynamic Property Hinting)
If a element in a template has dynamic class or text, the compiler attaches a bitwise "patch flag" to the render function output.
<!-- Template -->
<div :class="activeClass" id="header">
{{ message }}
</div>This compiles into a virtual node with a Patch Flag:
render() {
return createVNode("div", {
class: _ctx.activeClass,
id: "header"
}, _ctx.message,
9 /* PATCH FLAGS: 1 (TEXT) + 8 (CLASS) */)
}At runtime, the diffing algorithm reads the flag 9. Instead of comparing all attributes (like id which is static), it uses fast bitwise operations to check only the class and the text contents.
2. Static Hoisting (hoistStatic)
If a template block is entirely static (has no bindings), the compiler "hoists" the virtual node declaration outside the render function.
// Hoisted outside render function - created only once!
const _hoisted_1 = createVNode("div", { class: "info" }, [
createVNode("p", null, "Static text here"),
createVNode("span", null, "More static info")
])
render() {
return createVNode("div", null, [
_hoisted_1, // Re-used across re-renders
createVNode("span", null, _ctx.dynamicValue, 1 /* TEXT */)
])
}Benefits: The static virtual nodes are created once in memory during initialization and reused. Runtime diffing immediately skips them because their references are identical (oldVNode === newVNode).
3. Block Trees (Flat Diffing)
A "Block" is a virtual node container that has a flat list of nested dynamic children. The block tree is created automatically for any structural template block.
Instead of scanning an entire nested DOM structure, Vue 3 stores dynamic children in a flat array (dynamicChildren) inside the parent Block:
<div> <!-- Parent Block -->
<div>
<p>Static Text</p>
<span>{{ dynamicValue }}</span> <!-- Dynamic Node -->
</div>
</div>The compiled block representation:
{
tag: 'div',
children: [...],
dynamicChildren: [
{ tag: 'span', patchFlag: 1, children: dynamicValue }
]
}At runtime, Vue's diffing loop directly iterates over dynamicChildren, completely skipping the intermediate static div and p wrappers. This reduces the time complexity from $O(N)$ (where $N$ is total template elements) to $O(M)$ (where $M$ is only dynamic elements).
3. The Core Diffing Loop (The Key Algorithm)
When dynamic children lists need sorting, addition, or removal, Vue 3 implements a Fast Diff Algorithm inspired by inferno. It operates in five stages:
- Sync from Start: Compare nodes from the beginning of the list while keys match.
- Sync from End: Compare nodes from the end of the list backward while keys match.
- Common Node Addition: If all old nodes are checked but new nodes remain, mount the new nodes.
- Common Node Removal: If all new nodes are checked but old nodes remain, unmount the old nodes.
- Complex Reordering: If nodes have been moved, Vue finds the Longest Common Subsequence (LCS) of the dynamic array. Nodes inside the LCS are kept in place, while nodes outside the LCS are moved. This minimizes expensive DOM move operations.
4. Architectural Comparison: React vs Vue 3
| Metric | React (Fiber) | Vue 3 (Compiler-Informed) |
|---|---|---|
| Philosophy | Runtime-heavy; treats all elements as dynamic. | Compiler-informed; splits template into static and dynamic nodes. |
| Dynamic / Static Separation | None (unless manually optimized using React.memo). | Automated at compile time. |
| Virtual DOM Creation | Eagerly creates all Virtual Nodes on every single render. | Hoists static nodes; creates only dynamic nodes on re-render. |
| Diffing Cost | Proportional to the size of the entire tree. | Proportional only to the number of dynamic elements. |
