Day 21: Virtual DOM, Reconciliation Algorithm & React Fiber Architecture
Understand why React diffs a virtual tree instead of touching the DOM directly, and what Fiber actually changed about how that diffing happens.
Study
Concepts
Virtual DOM: a plain-object description of the UI
A Virtual DOM node is a lightweight plain JS object (`{ type, props, children }`) describing what the UI SHOULD look like. Real DOM mutations are expensive (layout, style recalculation, paint); comparing two plain-object trees in memory is cheap. React renders a new virtual tree on every update, diffs it against the previous one ("reconciliation"), and applies only the MINIMAL set of real DOM operations needed to catch up.
Reconciliation uses heuristics, not a theoretically-optimal tree diff (which is O(n³) and too slow): elements of a different TYPE at the same position are assumed completely different (old subtree unmounted, new one mounted fresh — state is lost); elements of the same type are compared prop-by-prop and updated in place; lists use the `key` prop to match items across renders instead of comparing by position — which is exactly why a missing/index-based key causes state to attach to the wrong item after a reorder.
Fiber: reconciliation you can pause, resume, and prioritize
Before React 16, reconciliation ("Stack Reconciler") walked the tree recursively and synchronously — once started, it could not be interrupted, so a large update could block the main thread and freeze user input for the duration. Fiber replaced the tree of virtual DOM nodes with a tree of "fiber" units of work — plain JS objects that ALSO form a linked list (child, sibling, return/parent pointers) so the engine can walk the tree iteratively instead of via recursive function calls.
Because each fiber is a discrete unit of work, React can process a few, yield control back to the browser to handle input/paint a frame, then resume from where it left off — this is what makes Concurrent Features (Day 23) possible. Work happens in two phases: Render/Reconciliation (interruptible — builds the "work-in-progress" fiber tree, diffs it, produces a list of DOM effects) and Commit (synchronous, NOT interruptible — actually mutates the DOM and runs layout effects, so the user never sees a half-applied update).
See It
Visualizations
Visualization
One React update, end to end
component function re-runs, returns new element tree
build work-in-progress fiber tree, diff vs current tree
apply the minimal DOM mutations, run layout effects
user sees the updated UI
Visualization
Why key matters when reordering a list
Without a stable key, React matches by position and reuses the wrong DOM node's state.
Build It
Code Examples
What a Virtual DOM node actually looks like
// This JSX...
const element = <h1 className="title">Hello, {name}</h1>;
// ...compiles (roughly) to this plain object via React.createElement:
const element = {
type: 'h1',
props: {
className: 'title',
children: ['Hello, ', name],
},
};
// Reconciliation just diffs two of these plain-object trees — no DOM involved.The key bug, reproduced
function TodoList({ items }) {
return (
<ul>
{items.map((item, index) => (
// BAD: index as key — reordering "items" reuses DOM nodes
// for the WRONG data, so any local state (e.g. an <input>)
// stays attached to the wrong row after a reorder.
<TodoRow key={index} item={item} />
))}
</ul>
);
}
function TodoListFixed({ items }) {
return (
<ul>
{items.map((item) => (
// GOOD: a stable, unique id lets React match the SAME
// element across renders regardless of position.
<TodoRow key={item.id} item={item} />
))}
</ul>
);
}Remember
Key Takeaways
- Virtual DOM diffing trades an in-memory object comparison for expensive real DOM mutations.
- Same type at the same position = update in place; different type = unmount old, mount new (state is lost).
- key lets React match list items across renders by identity, not position — always use a stable, unique id.
- Fiber turned recursive, synchronous reconciliation into an iterative, interruptible unit-of-work walk.
- Render phase is interruptible/can be thrown away; Commit phase is synchronous and always completes fully.
Do It
Practice
- 1Build a list with a text input per row, reorder the list using index keys, and watch the wrong input keep its typed value — then fix it with stable keys.
- 2Read the React source's ReactFiber.js type definition (or a summary) and list the 5 pointers every fiber holds (child, sibling, return, alternate, etc.).
- 3Explain, in your own words, why changing a component's root element type (e.g. <div> to <section>) resets all of its children's state.