React Architecture Under the Hood: Fiber Tree Reconciliation, Priority Scheduling, and Concurrent Mode
Deconstructing Fiber data structures, workLoop cycles, double buffering, lane priorities, and hydration traps
Part 5 in Series — Catch up on the previous article: Modern Web Networking: HTTP/2 Multiplexing, HTTP/3 QUIC, WebSockets, and Server-Sent Events (Part 4) before diving into this post.
Before React 16, React’s core reconciliation algorithm—retrospectively termed the Stack Reconciler—relied on a synchronous, recursive traversal of the component tree. When a state update occurred in a large component tree, React traversed the Virtual DOM recursively without yielding execution to the browser main thread. On complex UIs, this recursive work took 50ms to 200ms, completely blocking user input, animation frames, and network callbacks.
React 16 introduced a ground-up rewrite of the reconciliation architecture: the Fiber Engine. Fiber transforms reconciliation from a synchronous call stack into an interruptible, priority-scheduled cooperative scheduler.
1. The Fiber Data Structure: Units of Work
A Fiber is a plain JavaScript object representing a unit of work and a node in the component hierarchy. Instead of relying on JavaScript’s implicit runtime call stack, React Fiber implements a virtualized explicit stack frame using a singly-linked tree structure.
[ App Fiber ]
| child
v
[ Header Fiber ] ----sibling----> [ Main Content Fiber ]
| child | child
v v
[ Logo Fiber ] [ Card Fiber ]
| return | return
+---------------------------------+
Anatomical Definition of a Fiber Node
// Core Fiber Node Structure (Conceptual Representation)
interface FiberNode {
// DOM & Instance Identifiers
tag: WorkTag; // FunctionComponent, ClassComponent, HostComponent (DOM node)
key: string | null; // Reconciliation identifier
elementType: any; // Component function or HTML tag name ('div', 'span')
stateNode: any; // Reference to real DOM node or Class instance
// Singly-Linked Tree Pointers (Replaces Call Stack Recursion)
child: FiberNode | null; // First child
sibling: FiberNode | null; // Next sibling
return: FiberNode | null; // Parent Fiber (return target upon completion)
// Double-Buffering Pointer
alternate: FiberNode | null; // Points to matching node in Current <-> Work-in-Progress tree
// Side Effect & Queue Pointers
flags: Flags; // Bitmask (Placement, Update, Deletion)
lanes: Lanes; // Bitmask priority bitfield (SyncLane, InputContinuousLane, TransitionLane)
memoizedState: any; // Linked list of Hook objects for Function Components
updateQueue: any; // Pending state updates queue
}
2. Double Buffering & The Two-Phase Render Architecture
To prevent incomplete UI states from flashing on screen during asynchronous rendering, React Fiber uses a Double Buffering strategy mirroring graphics engines. React maintains two Fiber trees simultaneously in heap memory:
- Current Tree: Represents the UI currently painted on screen.
- Work-in-Progress (WIP) Tree: Built asynchronously in memory during background render phases.
[ Real Screen DOM ] <================== Mounted
^
|
[ Current Fiber Tree ] <--- alternate ---> [ Work-in-Progress Tree ]
^
| (Built asynchronously)
[ Background workLoop ]
The Two Execution Phases
Phase 1: Render Phase (Asynchronous & Interruptible)
performUnitOfWork() ---> beginWork() ---> completeWork()
(Computes Fiber diffs, marks flags bitfield, builds WIP tree)
----------------------------------- COMMIT BOUNDARY -----------------------------------
Phase 2: Commit Phase (Synchronous & Uninterruptible)
commitRoot() ---> commitBeforeMutationEffects() ---> commitMutationEffects() ---> commitLayoutEffects()
(Applies DOM insertions/deletions, invokes useLayoutEffect, swaps alternate pointer)
- Render Phase: Traverses the Fiber tree using
beginWork()(moving down to children) andcompleteWork()(moving up through siblings and parents). This phase produces a tree marked with side-effect flags (Placement,Update). It can be paused, aborted, or restarted by the scheduler if higher-priority input arrives. - Commit Phase: Executes synchronously in a single uninterrupted tick. The reconciler mutates the real DOM in
commitMutationEffects()and updatesalternatepointers, making the WIP tree the new Current tree.
3. Priority Scheduling & Concurrent Lanes
React 18 introduced Concurrent Mode, driven by a bitfield priority system known as Lanes. Instead of processing updates chronologically in a FIFO queue, React assigns update priorities based on user intent.
// React Priority Lanes (Bitfield Hierarchy)
const SyncLane: Lane = 0b0000000000000000000000000000001; // Discrete user inputs (click, keypress)
const InputContinuousLane: Lane = 0b0000000000000000000000000000100; // Continuous input (mousemove, scroll)
const DefaultLane: Lane = 0b0000000000000000000000100000000; // Data fetch completions, state transitions
const TransitionLane: Lane = 0b0000000000000000000010000000000; // Non-urgent UI transitions (useTransition)
const IdleLane: Lane = 0b0100000000000000000000000000000; // Off-screen pre-rendering
Deferring Heavy Renders with useTransition
When a user types into a search input that filters a 5,000-item table:
- Updating the text input requires
SyncLanepriority (immediate typing feedback). - Re-filtering the 5,000-item table should run at
TransitionLanepriority.
import React, { useState, useTransition } from "react";
export function SearchDashboard({ data }: { data: string[] }) {
const [query, setQuery] = useState("");
const [filteredResults, setFilteredResults] = useState(data);
const [isPending, startTransition] = useTransition();
const handleSearch = (e: React.ChangeEvent<HTMLInputElement>) => {
const nextQuery = e.target.value;
// High Priority Update (SyncLane): Immediate typing feedback
setQuery(nextQuery);
// Low Priority Update (TransitionLane): Interruptible background filtering
startTransition(() => {
const filtered = data.filter((item) => item.includes(nextQuery));
setFilteredResults(filtered);
});
};
return (
<div>
<input type="text" value={query} onChange={handleSearch} />
{isPending && <div className="spinner">Filtering background results...</div>}
<ResultList items={filteredResults} />
</div>
);
}
4. SSR Hydration Mismatches & Performance Traps
When using Server-Side Rendering (SSR), the server streams static HTML bytes to the client. During Hydration, React builds an in-memory Fiber tree and attempts to attach event listeners to matching HTML nodes already present in the DOM.
The Hydration Mismatch Trap
If the client-rendered output differs from the server-rendered HTML string (e.g., using new Date() or window.innerWidth during initial render), React detects a mismatch.
// BAD: Causes Server-Client Hydration Mismatch!
function HeaderClock() {
// Server renders timestamp at Build Time; Client renders at Hydration Time!
// Results in Hydration Mismatch Error & forced DOM discard!
return <div>Current Time: {new Date().toLocaleTimeString()}</div>;
}
// GOOD: Hydration-Safe Deferred Rendering
function SafeHeaderClock() {
const [time, setTime] = useState<string | null>(null);
useEffect(() => {
// Executes ONLY on client after initial hydration completes safely!
setTime(new Date().toLocaleTimeString());
}, []);
return <div>Current Time: {time ?? "Loading..."}</div>;
}
Summary & Key Takeaways
- Fiber Architecture: Replaces implicit JavaScript stack recursion with an explicit singly-linked tree (
child,sibling,return), enabling interruptible, cooperative scheduling. - Double Buffering: React builds a Work-in-Progress (WIP) tree asynchronously in memory and atomically swaps it onto the screen during the synchronous Commit phase.
- Lane Priorities: Bitfield priority levels allow high-priority user interactions (typing) to interrupt non-urgent background updates (large list filtering via
useTransition). - Hydration Safety: Ensure initial client render matches server-rendered HTML exactly to avoid expensive DOM re-creation penalties.
References & Further Reading
- React Architecture Architecture. React Fiber Architecture Design Document. Written by Andrew Clark. GitHub.
- React Official Docs. Concurrent React and Priority Scheduling. React Core Team.
- W3C. Cooperative Scheduling with requestIdleCallback. W3C Standard.
Part 6: State Management Architecture: Normalized Stores, Signals, XState Automata, and Immutability
Continue to Part 6 →