Not the DOM ItselfâBut Its Shadow: Understanding How Vue Diffs the Virtual DOM
Vue diffs the virtual DOM by comparing the previous and current virtual node trees, reusing nodes with the same type and key while updating only what changed, allowing it to efficiently transform the DOM with minimal operations.
When something changes in Vue, it feels immediate.
A number updates.
A list reorders.
A condition flips.
And the DOM reflects it.
Itâs tempting to imagine Vue reaching directly into the page and changing things as they happen.
But Vue doesnât work that way.
It doesnât operate on the DOM first.
It operates on a representation of it.
A Second Version of Reality
Every time your component renders, Vue creates a structureâa lightweight description of what the UI should look like.
Not actual DOM nodes.
Just objects that describe them.
Something like:
{
type: 'p',
children: '1'
}When state changes, Vue creates another version:
{
type: 'p',
children: '2'
}Now there are two versions of reality:
- what the UI was
- what the UI should become
And Vueâs job is not to rebuild everythingâ
but to transform one into the other.
The Question Is Not âWhat Changed?â
Itâs:
âWhat is the cheapest way to get from here to there?â
This is where diffing begins.
When Nothing Really Changes
If two nodes share the same type:
<p>1</p> â <p>2</p>Vue doesnât replace the <p>.
It updates only what matters:
- the text content
The structure stays.
Only the detail shifts.
When Everything Changes
But if the type changes:
<p>A</p> â <span>B</span>Thereâs nothing to reuse.
The meaning of the node itself is different.
So Vue replaces it entirely.
It doesnât try to be clever where identity is lost.
Where It Gets Complicated: Lists
The real challenge appears when you render lists:
<li v-for="item in items" :key="item.id">
{{ item.text }}
</li>Now imagine:
- before: A B C
- after: B A D
Same items, different order.
And one new item.
Identity Becomes Everything
Vue doesnât compare by position.
It compares by identity.
Thatâs what the key provides.
So Vue sees:
- A exists
- B exists
- C is gone
- D is new
Instead of rebuilding everything, it reuses what it can.
Moves what needs moving.
Creates what doesnât exist.
Removes what no longer belongs.
The Invisible Optimization
But thereâs another layer.
Vue tries to avoid unnecessary movement.
It looks for patternsâparts of the list that are already in the correct order.
And it keeps them in place.
Everything else adjusts around them.
You donât see this happening.
But itâs why updates feel fast, even when lists grow large.
When Keys Go Wrong
If you remove keysâor use something unstable like an indexâ
Vue loses identity.
Now it falls back to position:
- first becomes first
- second becomes second
Even if the underlying data changed order.
So A might become B.
B might become A.
And the DOM gets reused in ways that donât match reality.
Not brokenâbut subtly wrong.
Not Everything Is Compared
Vue doesnât blindly diff everything.
It already knows parts of your template will never change.
Static content is skipped entirely.
Only dynamic pieces are considered.
So the work is reduced before it even begins.
A System of Cooperation
At this point, the system becomes clearer.
Reactivity decides when to update.
Rendering produces a new description.
Diffing decides what actually changes.
The DOM is just the final step.
A Final Thought
Vue doesnât chase the DOM.
It rebuilds a shadow of it, compares that shadow to the previous one, and applies only the necessary changes.
What looks like instant updates is actually a careful negotiationâ
between what was,
what is,
and what can stay the same.