The Diff
You can now produce a fresh description of the whole UI after every change. Realizing the whole description every time would work - and throw away every input's text, every scroll position, every video's playback, while doing a page worth of work for a one-word change. The fix is the algorithm at the heart of React and Vue: compare the new description to the old one, and change only what differs. Today you write it - and personally trigger the bug that made every framework demand keys.
diff(): the recursive compare
Our diff walks two vnode trees and emits patch operations - console-friendly descriptions of what a real renderer would do to the DOM:
// --- a small change between two renders ---
const before = ;
const after = ;
.;
What just happened: one text patch. The whole page was re-described, but the diff found that only the heading's text differs - the button, its props, everything else produced zero operations. That's the virtual DOM bargain in one console line: describe everything, touch almost nothing. The rules you implemented - text compare, type mismatch = replace subtree, same type = patch props and recurse - are React's reconciliation heuristics, straight from their docs, in your handwriting.
Now break it
Step 5 matched children by position - old[0] vs new[0]. Watch what that does to a reordered
list. Same diff (compact), a list that moves its first item to the end:
const before = ;
// The user "moved" Buy milk to the bottom - same three items, one moved:
const after = ;
.;
console.log;
What just happened: three text rewrites for a single move. Position-matching decided
"item 0 changed its text from Buy milk to Walk dog" and so on down the list - it rewrote every
row's contents instead of moving one node. In a real DOM those rewrites destroy whatever lived
in those rows: input text, checkbox state, focus. This is - exactly, mechanically - the
index-key corruption bug from our
React guide's phase 4
(and Vue's, and Svelte's, and Angular's track rule). You've now caused it from the inside.
Your turn: fix it with keys
Give the differ identity to match by, instead of position. Add key support to the child loop:
const before = ;
const after = ;
.;
// Goal: exactly one MOVE (milk) - and zero rewrites. That's what keys buy.
When your version prints a single MOVE key=milk, you've written the reason every framework
demands stable keys: identity turns "rewrite three rows" into "move one node." (Real frameworks'
keyed algorithms also minimize which moves - longest-increasing-subsequence tricks - but the
identity insight is the whole foundation.)
Recap
- The diff: null checks, text compare, different type = replace, same type = patch props + recurse into children.
- One data change → one patch, even though the whole tree was re-described. That's the bargain.
- Positional child matching turns a reorder into a cascade of rewrites - you triggered the famous keys bug deliberately.
- Keys give the differ identity: match by key, and a move is a move.
- Everything our framework guides said about reconciliation and keys, you have now implemented.
← Phase 3: The Virtual DOM · Guide overview · Phase 5: Wiring It Together →