Most developers write React using JSX and hooks like useState and useEffect. But what actually happens when state changes? How does React transform a component tree into high-performance DOM mutations without blocking the main browser thread?
To understand React's speed and concurrency capabilities, we must look under the hood at the React Fiber engine, the Virtual DOM, and the Reconciliation algorithm.
1. The Virtual DOM Myth vs. Reality
For years, developers explained React as "a virtual copy of the real DOM." While true at a high level, the Virtual DOM is actually a tree of lightweight JavaScript objects (React Elements) that describe what the UI should look like at any point in time.
When state updates occur:
- React creates a new tree of React Elements (
JSX -> React.createElement). - React compares the new tree against the current tree (this process is called Diffing or Reconciliation).
- React computes the minimal set of DOM operations (inserts, updates, deletes) and applies them to the real browser DOM during the Commit Phase.
2. Why Fiber Was Invented
Before React 16, React used the Stack Reconciler. It recursed through component trees synchronously. Once a render started, it could not be interrupted until the entire DOM tree was reconciled. On complex pages, synchronous rendering caused frame drops and input lag (jank).
React Fiber was a complete ground-up rewrite of React's core algorithm. A Fiber is a plain JavaScript object that represents a unit of work.
// Simplified mental model of a Fiber Node
interface FiberNode {
type: any; // Component function, class, or element tag name ("div")
key: string | null;
stateNode: any; // Real DOM node instance
child: FiberNode | null; // First child
sibling: FiberNode | null; // Next sibling
return: FiberNode | null; // Parent fiber node
alternate: FiberNode | null; // Double buffering linked fiber
flags: number; // Mutation flags (Placement, Update, Deletion)
}
Because Fibers form a singly-linked list structure (Child -> Sibling -> Parent), React can yield execution back to the browser event loop, process urgent user interactions (like keypresses or mouse clicks), and resume rendering later!
3. The Two Phases of Rendering
React splits every render cycle into two distinct phases:
Phase 1: The Render / Reconciliation Phase (Asynchronous & Interruptible)
During the Render phase:
- React traverses the Fiber tree and computes changes.
- It builds a work-in-progress Fiber tree using a technique called Double Buffering.
- React marks Fiber nodes with side-effect flags (e.g.,
Placement,Update,ChildDeletion). - Crucial: This phase has zero DOM mutations. It can be paused, aborted, or restarted by React's Scheduler without visual artifacts.
Phase 2: The Commit Phase (Synchronous & Uninterruptible)
Once the Render phase completes:
- React takes the finished Fiber tree and applies all accumulated mutations to the real browser DOM in one fast, synchronous pass.
- Lifecycle methods (
componentDidMount,componentDidUpdate) anduseLayoutEffecthooks execute. useEffecthooks are scheduled asynchronously right after browser paint.
4. Reconciliation & Key Optimization Rules
React's heuristic diffing algorithm runs in O(n) time complexity based on two main assumptions:
- Different Element Types produce different trees: Changing a
divelement to asectionelement causes React to unmount the entire old subtree and build a new one from scratch. - Keys identify persistent elements across renders: Using stable, unique
keyprops allows React to match children across renders when items are reordered, inserted, or removed.
// Bad: Index as key leads to incorrect state retention & unnecessary DOM updates
{items.map((item, index) => (
<ListItem key={index} data={item} />
))}
// Good: Stable ID guarantees optimal reconciliation & Fiber node reuse
{items.map((item) => (
<ListItem key={item.id} data={item} />
))}
5. Architectural Takeaways for Developers
Understanding Fiber gives you actionable insight into performance tuning:
- Keep state localized: State changes trigger reconciliation from that component down its subtree. Localizing state prevents unnecessary Fiber tree traversals.
- Use
React.memo&useMemostrategically: Skipping reconciliation for unchanged subtrees saves Fiber traversal CPU cycles. - Never mutate state directly: React compares Fiber state references (
Object.is). Direct object mutations break diffing detection.