React Fiber Architecture: The Complete Technical Reference
React Fiber Architecture: The Complete Technical Reference
A Deep Dive into React's Reconciliation Engine, Scheduler, and Concurrent Rendering Model
Table of Contents
- Introduction: Why Fiber Exists
- The Stack Reconciler Problem
- Fiber: A Virtual Stack Frame
- The Fiber Node Data Structure
- Fiber Tree Topology
- Double Buffering: Current vs WorkInProgress
- The Work Loop
- Reconciliation Algorithm
- Priority, Lanes, and Scheduling
- The Scheduler Package
- Render Phase: Building the WorkInProgress Tree
- Commit Phase: Applying Mutations
- Effects System
- Hooks Implementation on Fiber
- Concurrent Features Deep Dive
- Suspense Internals
- Transitions and useDeferredValue
- Error Boundaries and Recovery
- Profiler and DevTools Integration
- Performance Patterns
- Source Code Navigation Guide
Introduction: Why Fiber Exists
React Fiber is a complete rewrite of React's core algorithm, introduced in React 16 (2017). The name "Fiber" comes from computer science terminology—a fiber is a lightweight thread of execution that can be paused, resumed, and prioritized.
The Core Innovation
┌─────────────────────────────────────────────────────────────────┐
│ Fiber's Key Capabilities │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 1. INCREMENTAL RENDERING │
│ Split rendering work into chunks, spread over frames │
│ │
│ 2. PAUSE AND RESUME │
│ Interrupt work-in-progress to handle higher priority │
│ │
│ 3. ABORT WORK │
│ Throw away work if no longer needed │
│ │
│ 4. REUSE WORK │
│ Previously completed work can be reused │
│ │
│ 5. PRIORITY SCHEDULING │
│ Different types of updates have different urgency │
│ │
└─────────────────────────────────────────────────────────────────┘
The Stack Reconciler Problem
React Pre-16 Architecture
Before Fiber, React used a stack-based recursive reconciler:
// Simplified pre-Fiber reconciliation (pseudocode)
function reconcile(element, container) {
const instance = instantiateComponent(element);
const markup = instance.mount(); // RECURSIVE - can't interrupt
container.appendChild(markup);
}
class CompositeComponent {
mount() {
const element = this.render(); // Call render
const child = instantiateComponent(element);
return child.mount(); // Recurse down - BLOCKING
}
}
Why Recursion Fails
┌─────────────────────────────────────────────────────────────────┐
│ Stack Reconciler Execution │
├─────────────────────────────────────────────────────────────────┤
│ │
│ Call Stack (can't be interrupted): │
│ │
│ mount(App) │
│ └─► mount(Layout) │
│ └─► mount(Header) │
│ └─► mount(Nav) │
│ └─► mount(NavItem) × 100 │
│ └─► mount(Link) │
│ └─► ... │
│ │
│ ═══════════════════════════════════════════════════════════ │
│ │
│ Frame Budget: 16.67ms (60fps) │
│ Deep tree mount: 50ms+ ──► FRAME DROP ──► JANK │
│ │
│ Problems: │
│ • Can't pause mid-recursion │
│ • Can't prioritize user input over rendering │
│ • Browser is blocked until entire tree completes │
│ │
└─────────────────────────────────────────────────────────────────┘
The Solution: Make Reconciliation Interruptible
Fiber transforms the recursive tree traversal into an iterative process using a linked-list data structure that can be paused at any node.
Fiber: A Virtual Stack Frame
A Fiber is essentially a JavaScript object that represents:
- A unit of work to be performed
- A component instance and its state
- A virtual stack frame that can be heap-allocated
Conceptual Model
┌─────────────────────────────────────────────────────────────────┐
│ Call Stack vs Fiber Stack │
├─────────────────────────────────────────────────────────────────┤
│ │
│ CALL STACK (JavaScript Engine) FIBER "STACK" (Heap) │
│ ┌───────────────────────┐ ┌───────────────────────┐ │
│ │ reconcileChildren() │ │ Fiber: App │ │
│ ├───────────────────────┤ │ ↓ child │ │
│ │ updateClassComponent()│ │ Fiber: Layout │ │
│ ├───────────────────────┤ │ ↓ child │ │
│ │ beginWork() │ │ Fiber: Header │ │
│ ├───────────────────────┤ │ ↓ child │ │
│ │ performUnitOfWork() │ │ Fiber: Nav ◄─ PAUSED │ │
│ ├───────────────────────┤ │ ↓ child │ │
│ │ workLoopConcurrent() │ │ Fiber: ... (pending) │ │
│ └───────────────────────┘ └───────────────────────┘ │
│ │
│ ✗ Can't pause/resume ✓ Can pause at any Fiber │
│ ✗ Implicit (engine-managed) ✓ Explicit (React-managed) │
│ ✗ Fixed priority ✓ Prioritized scheduling │
│ │
└─────────────────────────────────────────────────────────────────┘
The Fiber Node Data Structure
Every element in a React application has a corresponding Fiber node. Here's the complete structure:
// Source: packages/react-reconciler/src/ReactInternalTypes.js
interface Fiber {
// ═══════════════════════════════════════════════════════════
// IDENTITY
// ═══════════════════════════════════════════════════════════
tag: WorkTag; // Type of fiber (function, class, host, etc.)
key: null | string; // Unique identifier among siblings
elementType: any; // The original element type (function/class/string)
type: any; // Resolved type (after lazy resolution)
stateNode: any; // Local state:
// - DOM node for HostComponent
// - Class instance for ClassComponent
// - null for FunctionComponent
// ═══════════════════════════════════════════════════════════
// TREE STRUCTURE (Linked List)
// ═══════════════════════════════════════════════════════════
return: Fiber | null; // Parent fiber
child: Fiber | null; // First child fiber
sibling: Fiber | null; // Next sibling fiber
index: number; // Position among siblings
// ═══════════════════════════════════════════════════════════
// PROPS & STATE
// ═══════════════════════════════════════════════════════════
ref: RefObject | RefCallback | null;
pendingProps: any; // Props for this render
memoizedProps: any; // Props from last render
memoizedState: any; // State from last render
// - For FunctionComponent: hooks linked list
// - For ClassComponent: this.state
updateQueue: UpdateQueue | null; // Queue of state updates
// ═══════════════════════════════════════════════════════════
// EFFECTS
// ═══════════════════════════════════════════════════════════
flags: Flags; // Bitmask of side effects
subtreeFlags: Flags; // Aggregated flags from subtree
deletions: Array<Fiber> | null; // Children to be deleted
nextEffect: Fiber | null; // Next fiber with effects (legacy)
// ═══════════════════════════════════════════════════════════
// PRIORITY & SCHEDULING
// ═══════════════════════════════════════════════════════════
lanes: Lanes; // Priority lanes for this fiber
childLanes: Lanes; // Aggregated lanes from subtree
// ═══════════════════════════════════════════════════════════
// DOUBLE BUFFERING
// ═══════════════════════════════════════════════════════════
alternate: Fiber | null; // Pointer to current/workInProgress twin
// ═══════════════════════════════════════════════════════════
// DEV & PROFILING
// ═══════════════════════════════════════════════════════════
actualDuration?: number; // Time spent rendering this fiber
actualStartTime?: number; // When rendering started
selfBaseDuration?: number; // Duration without children
treeBaseDuration?: number; // Duration including subtree
_debugOwner?: Fiber | null; // Owner for debugging
_debugSource?: Source | null; // Source location
}
Work Tags
// Source: packages/react-reconciler/src/ReactWorkTags.js
const FunctionComponent = 0;
const ClassComponent = 1;
const IndeterminateComponent = 2; // Not yet determined (first render)
const HostRoot = 3; // Root of the fiber tree
const HostPortal = 4; // React.createPortal
const HostComponent = 5; // DOM elements ('div', 'span')
const HostText = 6; // Text nodes
const Fragment = 7;
const Mode = 8; // StrictMode, ConcurrentMode
const ContextConsumer = 9;
const ContextProvider = 10;
const ForwardRef = 11;
const Profiler = 12;
const SuspenseComponent = 13;
const MemoComponent = 14;
const SimpleMemoComponent = 15;
const LazyComponent = 16;
const IncompleteClassComponent = 17;
const DehydratedFragment = 18; // SSR hydration
const SuspenseListComponent = 19;
const ScopeComponent = 21;
const OffscreenComponent = 22; // Hidden content (transitions)
const LegacyHiddenComponent = 23;
const CacheComponent = 24;
const TracingMarkerComponent = 25;
Fiber Flags (Side Effects)
// Source: packages/react-reconciler/src/ReactFiberFlags.js
const NoFlags = 0b0000000000000000000000000000;
const PerformedWork = 0b0000000000000000000000000001;
const Placement = 0b0000000000000000000000000010; // Insert into DOM
const Update = 0b0000000000000000000000000100; // Update existing
const ChildDeletion = 0b0000000000000000000000010000; // Remove child
const ContentReset = 0b0000000000000000000000100000; // Reset text
const Callback = 0b0000000000000000000001000000; // Has callback
const DidCapture = 0b0000000000000000000010000000; // Error captured
const ForceClientRender = 0b0000000000000000000100000000; // Skip SSR
const Ref = 0b0000000000000000001000000000; // Has ref
const Snapshot = 0b0000000000000000010000000000; // getSnapshotBeforeUpdate
const Passive = 0b0000000000000000100000000000; // useEffect
const Hydrating = 0b0000000000000001000000000000; // SSR hydration
const Visibility = 0b0000000000000010000000000000; // Offscreen
const StoreConsistency = 0b0000000000000100000000000000; // useSyncExternalStore
// Composite flags
const LayoutMask = Update | Callback | Ref | Visibility;
const PassiveMask = Passive | ChildDeletion;
Fiber Tree Topology
Linked List Structure
Unlike a traditional tree with arrays of children, Fiber uses a linked list with three pointers:
┌─────────────────────────────────────────────────────────────────┐
│ Fiber Tree Topology │
├─────────────────────────────────────────────────────────────────┤
│ │
│ React Elements: Fiber Linked List: │
│ │
│ <App> ┌───────────┐ │
│ / \ │ App │ │
│ <Header> <Main> │ tag: 0 │ │
│ | | └─────┬─────┘ │
│ <Logo> <Content> │ child │
│ ▼ │
│ ┌───────────┐ sibling ┌─────────┐
│ │ Header │──────────►│ Main │
│ │ tag: 5 │ │ tag: 5 │
│ └─────┬─────┘ └────┬────┘
│ │ child │ child
│ ▼ ▼
│ ┌───────────┐ ┌──────────┐
│ │ Logo │ │ Content │
│ │ tag: 5 │ │ tag: 5 │
│ └───────────┘ └──────────┘
│ │
│ Navigation: child ↓ sibling → return ↑ │
│ │
└─────────────────────────────────────────────────────────────────┘
Why Linked List?
// Array-based children (traditional)
// Finding next unit of work: O(n) - need parent reference + index
// Linked list (Fiber)
// Finding next unit of work: O(1) - just follow pointer
function getNextFiber(fiber) {
// 1. Try to go to child
if (fiber.child) return fiber.child;
// 2. Try to go to sibling
let current = fiber;
while (current) {
if (current.sibling) return current.sibling;
// 3. Go up and try sibling of parent
current = current.return;
}
return null; // Done with entire tree
}
Double Buffering: Current vs WorkInProgress
React maintains two fiber trees at any time:
┌─────────────────────────────────────────────────────────────────┐
│ Double Buffering │
├─────────────────────────────────────────────────────────────────┤
│ │
│ CURRENT TREE WORK-IN-PROGRESS TREE │
│ (What's on screen) (Being built) │
│ │
│ ┌─────────┐ alternate ┌─────────┐ │
│ │ App │◄──────────────────►│ App │ │
│ │ current │ │ WIP │ │
│ └────┬────┘ └────┬────┘ │
│ │ │ │
│ ┌────┴────┐ ┌────┴────┐ │
│ │ Header │◄──────────────────►│ Header │ │
│ └────┬────┘ └────┬────┘ │
│ │ │ │
│ ┌────┴────┐ ┌────┴────┐ │
│ │ Logo │◄──────────────────►│ Logo │ ◄── Being │
│ │ "v1.0" │ │ "v2.0" │ updated │
│ └─────────┘ └─────────┘ │
│ │
│ After commit: trees swap roles │
│ WIP becomes Current, old Current becomes next WIP │
│ │
└─────────────────────────────────────────────────────────────────┘
Implementation
// Creating workInProgress from current
function createWorkInProgress(current, pendingProps) {
let workInProgress = current.alternate;
if (workInProgress === null) {
// First render: create new fiber
workInProgress = createFiber(
current.tag,
pendingProps,
current.key,
current.mode
);
workInProgress.elementType = current.elementType;
workInProgress.type = current.type;
workInProgress.stateNode = current.stateNode;
// Link alternates
workInProgress.alternate = current;
current.alternate = workInProgress;
} else {
// Subsequent render: reuse fiber, reset fields
workInProgress.pendingProps = pendingProps;
workInProgress.type = current.type;
// Reset effect flags
workInProgress.flags = NoFlags;
workInProgress.subtreeFlags = NoFlags;
workInProgress.deletions = null;
}
// Copy over fields
workInProgress.lanes = current.lanes;
workInProgress.child = current.child;
workInProgress.memoizedProps = current.memoizedProps;
workInProgress.memoizedState = current.memoizedState;
workInProgress.updateQueue = current.updateQueue;
return workInProgress;
}
The Work Loop
The work loop is the heart of Fiber's execution model. It processes fibers one at a time, yielding control back to the browser between units of work.
Synchronous Work Loop
// Source: packages/react-reconciler/src/ReactFiberWorkLoop.js
function workLoopSync() {
// Process fibers until none remain
while (workInProgress !== null) {
performUnitOfWork(workInProgress);
}
}
Concurrent Work Loop
function workLoopConcurrent() {
// Process fibers while there's time remaining
while (workInProgress !== null && !shouldYield()) {
performUnitOfWork(workInProgress);
}
}
// shouldYield checks if browser needs control back
function shouldYield() {
const currentTime = getCurrentTime();
return currentTime >= deadline; // 5ms default time slice
}
Unit of Work Execution
function performUnitOfWork(unitOfWork) {
const current = unitOfWork.alternate;
// ═══════════════════════════════════════════════════════════
// BEGIN PHASE: Process this fiber, create children
// ═══════════════════════════════════════════════════════════
const next = beginWork(current, unitOfWork, renderLanes);
// Memoize props after processing
unitOfWork.memoizedProps = unitOfWork.pendingProps;
if (next === null) {
// ═══════════════════════════════════════════════════════════
// COMPLETE PHASE: No children, complete this fiber
// ═══════════════════════════════════════════════════════════
completeUnitOfWork(unitOfWork);
} else {
// Move to child
workInProgress = next;
}
}
Visual Execution Flow
┌─────────────────────────────────────────────────────────────────┐
│ Work Loop Execution │
├─────────────────────────────────────────────────────────────────┤
│ │
│ Tree: Execution Order: │
│ │
│ A 1. beginWork(A) │
│ / \ 2. beginWork(B) │
│ B C 3. beginWork(D) │
│ / \ \ 4. completeWork(D) ← no children │
│ D E F 5. beginWork(E) │
│ 6. completeWork(E) ← no children │
│ 7. completeWork(B) ← children done │
│ 8. beginWork(C) │
│ 9. beginWork(F) │
│ 10. completeWork(F) ← no children │
│ 11. completeWork(C) ← children done │
│ 12. completeWork(A) ← children done │
│ │
│ Pattern: Depth-first, begin going down, complete going up │
│ │
└─────────────────────────────────────────────────────────────────┘
Reconciliation Algorithm
beginWork: Processing a Fiber
// Source: packages/react-reconciler/src/ReactFiberBeginWork.js
function beginWork(
current: Fiber | null,
workInProgress: Fiber,
renderLanes: Lanes
): Fiber | null {
// ═══════════════════════════════════════════════════════════
// BAILOUT: Check if we can skip this fiber
// ═══════════════════════════════════════════════════════════
if (current !== null) {
const oldProps = current.memoizedProps;
const newProps = workInProgress.pendingProps;
if (
oldProps === newProps &&
!hasContextChanged() &&
(workInProgress.lanes & renderLanes) === NoLanes
) {
// No work to do, clone children and bail out
return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
}
}
// ═══════════════════════════════════════════════════════════
// WORK: Process based on fiber type
// ═══════════════════════════════════════════════════════════
switch (workInProgress.tag) {
case FunctionComponent:
return updateFunctionComponent(
current,
workInProgress,
workInProgress.type,
workInProgress.pendingProps,
renderLanes
);
case ClassComponent:
return updateClassComponent(
current,
workInProgress,
workInProgress.type,
workInProgress.pendingProps,
renderLanes
);
case HostRoot:
return updateHostRoot(current, workInProgress, renderLanes);
case HostComponent:
return updateHostComponent(current, workInProgress, renderLanes);
case HostText:
return updateHostText(current, workInProgress);
case SuspenseComponent:
return updateSuspenseComponent(current, workInProgress, renderLanes);
case MemoComponent:
return updateMemoComponent(
current,
workInProgress,
workInProgress.type,
workInProgress.pendingProps,
renderLanes
);
// ... more cases
}
}
Child Reconciliation (Diffing)
// Source: packages/react-reconciler/src/ReactChildFiber.js
function reconcileChildFibers(
returnFiber: Fiber,
currentFirstChild: Fiber | null,
newChild: any,
lanes: Lanes
): Fiber | null {
// ═══════════════════════════════════════════════════════════
// SINGLE ELEMENT
// ═══════════════════════════════════════════════════════════
if (typeof newChild === 'object' && newChild !== null) {
switch (newChild.$$typeof) {
case REACT_ELEMENT_TYPE:
return placeSingleChild(
reconcileSingleElement(
returnFiber,
currentFirstChild,
newChild,
lanes
)
);
case REACT_PORTAL_TYPE:
return placeSingleChild(
reconcileSinglePortal(
returnFiber,
currentFirstChild,
newChild,
lanes
)
);
case REACT_LAZY_TYPE:
// Handle lazy components
// ...
}
// ═══════════════════════════════════════════════════════════
// ARRAY OF ELEMENTS
// ═══════════════════════════════════════════════════════════
if (isArray(newChild)) {
return reconcileChildrenArray(
returnFiber,
currentFirstChild,
newChild,
lanes
);
}
// ═══════════════════════════════════════════════════════════
// ITERATOR
// ═══════════════════════════════════════════════════════════
if (getIteratorFn(newChild)) {
return reconcileChildrenIterator(
returnFiber,
currentFirstChild,
newChild,
lanes
);
}
}
// ═══════════════════════════════════════════════════════════
// TEXT CONTENT
// ═══════════════════════════════════════════════════════════
if (typeof newChild === 'string' || typeof newChild === 'number') {
return placeSingleChild(
reconcileSingleTextNode(
returnFiber,
currentFirstChild,
'' + newChild,
lanes
)
);
}
// ═══════════════════════════════════════════════════════════
// DELETE REMAINING CHILDREN
// ═══════════════════════════════════════════════════════════
return deleteRemainingChildren(returnFiber, currentFirstChild);
}
Array Reconciliation (The Diffing Algorithm)
function reconcileChildrenArray(
returnFiber: Fiber,
currentFirstChild: Fiber | null,
newChildren: Array<any>,
lanes: Lanes
): Fiber | null {
let resultingFirstChild: Fiber | null = null;
let previousNewFiber: Fiber | null = null;
let oldFiber = currentFirstChild;
let lastPlacedIndex = 0;
let newIdx = 0;
let nextOldFiber = null;
// ═══════════════════════════════════════════════════════════
// PHASE 1: Walk both lists, matching by index
// ═══════════════════════════════════════════════════════════
for (; oldFiber !== null && newIdx < newChildren.length; newIdx++) {
if (oldFiber.index > newIdx) {
// Old fiber is ahead, no match at this index
nextOldFiber = oldFiber;
oldFiber = null;
} else {
nextOldFiber = oldFiber.sibling;
}
// Try to reuse or create fiber
const newFiber = updateSlot(
returnFiber,
oldFiber,
newChildren[newIdx],
lanes
);
if (newFiber === null) {
// Keys don't match, break to phase 2
if (oldFiber === null) {
oldFiber = nextOldFiber;
}
break;
}
if (shouldTrackSideEffects) {
if (oldFiber && newFiber.alternate === null) {
// New fiber didn't reuse old, delete old
deleteChild(returnFiber, oldFiber);
}
}
lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);
if (previousNewFiber === null) {
resultingFirstChild = newFiber;
} else {
previousNewFiber.sibling = newFiber;
}
previousNewFiber = newFiber;
oldFiber = nextOldFiber;
}
// ═══════════════════════════════════════════════════════════
// PHASE 2: New children exhausted, delete remaining old
// ═══════════════════════════════════════════════════════════
if (newIdx === newChildren.length) {
deleteRemainingChildren(returnFiber, oldFiber);
return resultingFirstChild;
}
// ═══════════════════════════════════════════════════════════
// PHASE 3: Old children exhausted, add remaining new
// ═══════════════════════════════════════════════════════════
if (oldFiber === null) {
for (; newIdx < newChildren.length; newIdx++) {
const newFiber = createChild(returnFiber, newChildren[newIdx], lanes);
if (newFiber === null) continue;
lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);
if (previousNewFiber === null) {
resultingFirstChild = newFiber;
} else {
previousNewFiber.sibling = newFiber;
}
previousNewFiber = newFiber;
}
return resultingFirstChild;
}
// ═══════════════════════════════════════════════════════════
// PHASE 4: Keys don't match, use map for O(1) lookup
// ═══════════════════════════════════════════════════════════
const existingChildren = mapRemainingChildren(returnFiber, oldFiber);
for (; newIdx < newChildren.length; newIdx++) {
const newFiber = updateFromMap(
existingChildren,
returnFiber,
newIdx,
newChildren[newIdx],
lanes
);
if (newFiber !== null) {
if (shouldTrackSideEffects) {
if (newFiber.alternate !== null) {
// Reused fiber, remove from map
existingChildren.delete(
newFiber.key === null ? newIdx : newFiber.key
);
}
}
lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);
if (previousNewFiber === null) {
resultingFirstChild = newFiber;
} else {
previousNewFiber.sibling = newFiber;
}
previousNewFiber = newFiber;
}
}
// Delete any remaining old children not reused
if (shouldTrackSideEffects) {
existingChildren.forEach(child => deleteChild(returnFiber, child));
}
return resultingFirstChild;
}
Visual Diffing Example
┌─────────────────────────────────────────────────────────────────┐
│ Array Reconciliation │
├─────────────────────────────────────────────────────────────────┤
│ │
│ Old: [A] → [B] → [C] → [D] │
│ New: [A] → [C] → [D] → [B] │
│ │
│ Phase 1: Walk in order │
│ - A matches A ✓ │
│ - B vs C - keys don't match, break │
│ │
│ Phase 4: Build map {B: FiberB, C: FiberC, D: FiberD} │
│ - Look up C in map ✓ → reuse FiberC │
│ - Look up D in map ✓ → reuse FiberD │
│ - Look up B in map ✓ → reuse FiberB (moved) │
│ │
│ Result: FiberA → FiberC → FiberD → FiberB │
│ Effects: B marked for Placement (moved) │
│ │
└─────────────────────────────────────────────────────────────────┘
Priority, Lanes, and Scheduling
The Lanes Model
React uses a bitmask-based priority system called Lanes. Each lane represents a priority level, and multiple updates can be batched into the same lane.
// Source: packages/react-reconciler/src/ReactFiberLane.js
type Lanes = number;
type Lane = number;
const TotalLanes = 31; // 31 bits available
// Lane values (higher = lower priority)
const NoLanes: Lanes = 0b0000000000000000000000000000000;
const NoLane: Lane = 0b0000000000000000000000000000000;
const SyncLane: Lane = 0b0000000000000000000000000000001; // Highest
const InputContinuousLane: Lane = 0b0000000000000000000000000000100;
const DefaultLane: Lane = 0b0000000000000000000000000010000;
const TransitionLanes: Lanes = 0b0000000001111111111111111000000;
const IdleLane: Lane = 0b0010000000000000000000000000000;
const OffscreenLane: Lane = 0b0100000000000000000000000000000; // Lowest
Priority Levels Explained
┌─────────────────────────────────────────────────────────────────┐
│ Lane Priority Hierarchy │
├─────────────────────────────────────────────────────────────────┤
│ │
│ SYNC LANE (0b001) Highest Priority │
│ ├── Legacy mode synchronous updates │
│ ├── useLayoutEffect │
│ └── flushSync() │
│ │
│ INPUT CONTINUOUS LANE (0b100) High Priority │
│ ├── Drag events │
│ ├── Scroll events │
│ └── Mouse move │
│ │
│ DEFAULT LANE (0b10000) Normal Priority │
│ ├── setState from event handlers │
│ ├── Network responses │
│ └── Most updates │
│ │
│ TRANSITION LANES (0b1...1000000) Low Priority │
│ ├── startTransition updates │
│ ├── Navigation │
│ └── Large non-urgent updates │
│ │
│ IDLE LANE Lowest Priority │
│ └── Background work, prefetching │
│ │
│ OFFSCREEN LANE Hidden Content │
│ └── Pre-rendering hidden content │
│ │
└─────────────────────────────────────────────────────────────────┘
Lane Operations
// Check if lanes include a specific lane
function includesSomeLane(a: Lanes, b: Lanes): boolean {
return (a & b) !== NoLanes;
}
// Merge lanes (set union)
function mergeLanes(a: Lanes, b: Lanes): Lanes {
return a | b;
}
// Remove lanes
function removeLanes(set: Lanes, subset: Lanes): Lanes {
return set & ~subset;
}
// Get highest priority lane
function getHighestPriorityLane(lanes: Lanes): Lane {
return lanes & -lanes; // Isolate rightmost set bit
}
// Example:
// lanes = 0b10110
// -lanes = 0b01010 (two's complement)
// lanes & -lanes = 0b00010 (rightmost bit)
Entanglement
When lanes are entangled, they must be processed together:
// If an update in TransitionLane reads from SyncLane,
// they become entangled
function entangleLanes(root, lanes) {
const entanglements = root.entanglements;
let lanes = nextLanes;
while (lanes > 0) {
const index = pickArbitraryLaneIndex(lanes);
const lane = 1 << index;
// Add current lanes to this lane's entanglement set
entanglements[index] |= lanes;
lanes &= ~lane;
}
}
The Scheduler Package
React uses a separate scheduler package for time-slicing and priority management.
Scheduler Architecture
┌─────────────────────────────────────────────────────────────────┐
│ Scheduler Internals │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌───────────────────────────────────────────────────────────┐ │
│ │ Task Queues │ │
│ │ │ │
│ │ taskQueue (Min Heap by expirationTime) │ │
│ │ ┌────┬────┬────┬────┬────┐ │ │
│ │ │ T1 │ T2 │ T3 │ T4 │ T5 │ Sorted by expiration │ │
│ │ └────┴────┴────┴────┴────┘ │ │
│ │ │ │
│ │ timerQueue (Min Heap by startTime) │ │
│ │ ┌────┬────┬────┐ │ │
│ │ │ D1 │ D2 │ D3 │ Delayed tasks (not yet ready) │ │
│ │ └────┴────┴────┘ │ │
│ │ │ │
│ └───────────────────────────────────────────────────────────┘ │
│ │
│ ┌───────────────────────────────────────────────────────────┐ │
│ │ Work Loop │ │
│ │ │ │
│ │ while (task && !shouldYield) { │ │
│ │ task.callback(); │ │
│ │ task = peek(taskQueue); │ │
│ │ } │ │
│ │ │ │
│ │ // Yield to browser via MessageChannel │ │
│ │ // Resume in next macrotask │ │
│ │ │ │
│ └───────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
Priority Timeouts
// Source: packages/scheduler/src/SchedulerPriorities.js
const ImmediatePriority = 1;
const UserBlockingPriority = 2;
const NormalPriority = 3;
const LowPriority = 4;
const IdlePriority = 5;
// Timeouts determine when task "expires" and must run
const IMMEDIATE_PRIORITY_TIMEOUT = -1; // Sync, run now
const USER_BLOCKING_PRIORITY_TIMEOUT = 250; // 250ms
const NORMAL_PRIORITY_TIMEOUT = 5000; // 5 seconds
const LOW_PRIORITY_TIMEOUT = 10000; // 10 seconds
const IDLE_PRIORITY_TIMEOUT = maxSigned31BitInt; // Never expires
MessageChannel for Yielding
// Source: packages/scheduler/src/forks/SchedulerDOM.js
const channel = new MessageChannel();
const port = channel.port2;
channel.port1.onmessage = performWorkUntilDeadline;
function requestHostCallback(callback) {
scheduledHostCallback = callback;
if (!isMessageLoopRunning) {
isMessageLoopRunning = true;
port.postMessage(null); // Schedule macrotask
}
}
function performWorkUntilDeadline() {
if (scheduledHostCallback !== null) {
const currentTime = getCurrentTime();
// Yield after 5ms (default time slice)
deadline = currentTime + yieldInterval;
const hasMoreWork = scheduledHostCallback(true, currentTime);
if (!hasMoreWork) {
isMessageLoopRunning = false;
scheduledHostCallback = null;
} else {
// More work, schedule next slice
port.postMessage(null);
}
}
}
Why MessageChannel?
┌─────────────────────────────────────────────────────────────────┐
│ Yielding Mechanism Comparison │
├─────────────────────────────────────────────────────────────────┤
│ │
│ setTimeout(fn, 0): │
│ ✗ Minimum 4ms delay in browsers │
│ ✗ Throttled in background tabs │
│ │
│ requestAnimationFrame: │
│ ✗ Tied to paint cycle (~16ms) │
│ ✗ Paused in background tabs │
│ │
│ requestIdleCallback: │
│ ✗ Not available in all browsers │
│ ✗ Unpredictable timing │
│ │
│ MessageChannel: │
│ ✓ Runs in macrotask queue │
│ ✓ No artificial delays │
│ ✓ Consistent timing │
│ ✓ Works in all environments │
│ │
└─────────────────────────────────────────────────────────────────┘
Render Phase: Building the WorkInProgress Tree
The render phase is pure and can be interrupted. It produces a tree of fibers with effect flags.
Render Phase Flow
┌─────────────────────────────────────────────────────────────────┐
│ Render Phase │
├─────────────────────────────────────────────────────────────────┤
│ │
│ Entry Points: │
│ • scheduleUpdateOnFiber() ─── triggered by setState, etc. │
│ • performSyncWorkOnRoot() ── synchronous mode │
│ • performConcurrentWorkOnRoot() ── concurrent mode │
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ prepareFreshStack() │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ workLoopSync() or workLoopConcurrent() │ │
│ │ │ │ │
│ │ ├──► performUnitOfWork() │ │
│ │ │ │ │ │
│ │ │ ├──► beginWork() ← Process fiber │ │
│ │ │ │ │ │ │
│ │ │ │ └──► reconcileChildren() │ │
│ │ │ │ │ │
│ │ │ └──► completeUnitOfWork() │ │
│ │ │ │ │ │
│ │ │ └──► completeWork() │ │
│ │ │ │ │ │
│ │ │ └──► Create DOM │ │
│ │ │ Diff props │ │
│ │ │ Bubble flags │ │
│ │ │ │ │
│ │ └──► (repeat until workInProgress === null) │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ Output: WorkInProgress tree with effect flags │
│ │
└─────────────────────────────────────────────────────────────────┘
completeWork: Creating DOM and Diffing Props
// Source: packages/react-reconciler/src/ReactFiberCompleteWork.js
function completeWork(
current: Fiber | null,
workInProgress: Fiber,
renderLanes: Lanes
): Fiber | null {
const newProps = workInProgress.pendingProps;
switch (workInProgress.tag) {
case HostComponent: {
const type = workInProgress.type;
if (current !== null && workInProgress.stateNode != null) {
// ═══════════════════════════════════════════════════════
// UPDATE: Diff props
// ═══════════════════════════════════════════════════════
const oldProps = current.memoizedProps;
const updatePayload = diffProperties(
workInProgress.stateNode,
type,
oldProps,
newProps
);
// updatePayload = ['className', 'new-class', 'style', {...}]
workInProgress.updateQueue = updatePayload;
if (updatePayload) {
markUpdate(workInProgress);
}
} else {
// ═══════════════════════════════════════════════════════
// MOUNT: Create DOM instance
// ═══════════════════════════════════════════════════════
const instance = createInstance(
type,
newProps,
rootContainerInstance,
workInProgress
);
// Append all children to this instance
appendAllChildren(instance, workInProgress);
workInProgress.stateNode = instance;
// Set initial properties
finalizeInitialChildren(instance, type, newProps);
}
// ═══════════════════════════════════════════════════════
// BUBBLE FLAGS: Aggregate subtree effects
// ═══════════════════════════════════════════════════════
bubbleProperties(workInProgress);
return null;
}
case HostText: {
const newText = newProps;
if (current !== null && workInProgress.stateNode != null) {
// Update text content
const oldText = current.memoizedProps;
if (oldText !== newText) {
markUpdate(workInProgress);
}
} else {
// Create text node
workInProgress.stateNode = createTextInstance(newText);
}
bubbleProperties(workInProgress);
return null;
}
// ... more cases
}
}
Bubbling Subtree Flags
function bubbleProperties(completedWork: Fiber) {
let subtreeFlags = NoFlags;
let child = completedWork.child;
while (child !== null) {
// Aggregate flags from children
subtreeFlags |= child.subtreeFlags;
subtreeFlags |= child.flags;
child = child.sibling;
}
completedWork.subtreeFlags |= subtreeFlags;
}
This enables O(1) checks during commit: if subtreeFlags === NoFlags, the entire subtree can be skipped.
Commit Phase: Applying Mutations
The commit phase is synchronous and cannot be interrupted. It applies effects to the DOM.
Three Sub-Phases
┌─────────────────────────────────────────────────────────────────┐
│ Commit Phase │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌───────────────────────────────────────────────────────────┐ │
│ │ 1. BEFORE MUTATION (Read Phase) │ │
│ │ • getSnapshotBeforeUpdate lifecycle │ │
│ │ • Read DOM state before changes │ │
│ └───────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌───────────────────────────────────────────────────────────┐ │
│ │ 2. MUTATION (Write Phase) │ │
│ │ • Insert new DOM nodes │ │
│ │ • Update existing DOM nodes │ │
│ │ • Delete removed DOM nodes │ │
│ │ • Detach refs │ │
│ └───────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌───────────────────────────────────────────────────────────┐ │
│ │ 3. LAYOUT (After Mutation) │ │
│ │ • componentDidMount │ │
│ │ • componentDidUpdate │ │
│ │ • useLayoutEffect callbacks │ │
│ │ • Attach refs │ │
│ └───────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌───────────────────────────────────────────────────────────┐ │
│ │ 4. PASSIVE EFFECTS (Async, after paint) │ │
│ │ • useEffect callbacks │ │
│ │ • Scheduled via scheduler │ │
│ └───────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
Commit Implementation
// Source: packages/react-reconciler/src/ReactFiberCommitWork.js
function commitRoot(root) {
const finishedWork = root.finishedWork;
const lanes = root.finishedLanes;
// Reset for next render
root.finishedWork = null;
root.finishedLanes = NoLanes;
// ═══════════════════════════════════════════════════════════
// PHASE 1: Before Mutation
// ═══════════════════════════════════════════════════════════
commitBeforeMutationEffects(root, finishedWork);
// ═══════════════════════════════════════════════════════════
// PHASE 2: Mutation
// ═══════════════════════════════════════════════════════════
commitMutationEffects(root, finishedWork, lanes);
// Swap trees: workInProgress becomes current
root.current = finishedWork;
// ═══════════════════════════════════════════════════════════
// PHASE 3: Layout
// ═══════════════════════════════════════════════════════════
commitLayoutEffects(finishedWork, root, lanes);
// ═══════════════════════════════════════════════════════════
// PHASE 4: Schedule Passive Effects
// ═══════════════════════════════════════════════════════════
if (rootDoesHavePassiveEffects) {
scheduleCallback(NormalSchedulerPriority, () => {
flushPassiveEffects();
return null;
});
}
}
DOM Mutations
function commitMutationEffectsOnFiber(
finishedWork: Fiber,
root: FiberRoot,
lanes: Lanes
) {
const flags = finishedWork.flags;
switch (finishedWork.tag) {
case HostComponent: {
recursivelyTraverseMutationEffects(root, finishedWork, lanes);
commitReconciliationEffects(finishedWork);
if (flags & Update) {
const instance = finishedWork.stateNode;
const updatePayload = finishedWork.updateQueue;
finishedWork.updateQueue = null;
if (updatePayload !== null) {
// Apply property changes
// updatePayload = ['className', 'new', 'style', {...}]
commitUpdate(instance, updatePayload, type, oldProps, newProps);
}
}
return;
}
case HostText: {
const textInstance = finishedWork.stateNode;
const newText = finishedWork.memoizedProps;
commitTextUpdate(textInstance, oldText, newText);
return;
}
}
}
function commitReconciliationEffects(finishedWork: Fiber) {
const flags = finishedWork.flags;
if (flags & Placement) {
commitPlacement(finishedWork);
finishedWork.flags &= ~Placement;
}
}
function commitPlacement(finishedWork: Fiber) {
const parentFiber = getHostParentFiber(finishedWork);
const parent = parentFiber.stateNode;
const before = getHostSibling(finishedWork);
insertOrAppendPlacementNode(finishedWork, before, parent);
}
Effects System
Effect List (Legacy) vs Subtree Flags (Modern)
┌─────────────────────────────────────────────────────────────────┐
│ Effect Tracking Evolution │
├─────────────────────────────────────────────────────────────────┤
│ │
│ LEGACY (React 16-17): Effect List │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ Fibers with effects linked via nextEffect pointer: │ │
│ │ │ │
│ │ firstEffect ─► FiberA ─► FiberC ─► FiberE ─► null │ │
│ │ │ │ │ │ │
│ │ Update Placement Deletion │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ MODERN (React 18+): Subtree Flags │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ Flags bubble up, traverse down only if subtreeFlags: │ │
│ │ │ │
│ │ App (subtreeFlags: Update|Placement) │ │
│ │ │ │ │
│ │ ┌───┴───┐ │ │
│ │ A(Update) B(subtreeFlags: Placement) │ │
│ │ │ │ │
│ │ C(Placement) │ │
│ │ │ │
│ │ Skip subtrees with NoFlags entirely │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
Hooks Implementation on Fiber
Hooks are stored as a linked list on the fiber's memoizedState.
Hook Data Structure
interface Hook {
memoizedState: any; // Current state value
baseState: any; // State before pending updates
baseQueue: Update<any> | null;// Updates not yet processed
queue: UpdateQueue<any> | null; // Pending updates
next: Hook | null; // Next hook in linked list
}
// Example: Component with 3 hooks
function MyComponent() {
const [count, setCount] = useState(0); // Hook 1
const [name, setName] = useState(''); // Hook 2
const memoized = useMemo(() => calc(), []); // Hook 3
}
// Fiber.memoizedState:
// Hook1 { memoizedState: 0, next: ─┐
// │
// Hook2 { memoizedState: '', next: ┼─┐
// │
// Hook3 { memoizedState: result, next: null }
Hook Dispatcher
React uses different dispatchers for mount vs update:
// Source: packages/react-reconciler/src/ReactFiberHooks.js
const HooksDispatcherOnMount = {
useState: mountState,
useEffect: mountEffect,
useRef: mountRef,
useMemo: mountMemo,
useCallback: mountCallback,
// ...
};
const HooksDispatcherOnUpdate = {
useState: updateState,
useEffect: updateEffect,
useRef: updateRef,
useMemo: updateMemo,
useCallback: updateCallback,
// ...
};
function renderWithHooks(
current: Fiber | null,
workInProgress: Fiber,
Component: any,
props: any,
secondArg: any,
nextRenderLanes: Lanes
) {
renderLanes = nextRenderLanes;
currentlyRenderingFiber = workInProgress;
// Reset hooks state
workInProgress.memoizedState = null;
workInProgress.updateQueue = null;
// Choose dispatcher
ReactCurrentDispatcher.current =
current === null || current.memoizedState === null
? HooksDispatcherOnMount
: HooksDispatcherOnUpdate;
// Call component
let children = Component(props, secondArg);
// Reset
ReactCurrentDispatcher.current = ContextOnlyDispatcher;
return children;
}
useState Implementation
function mountState<S>(initialState: S): [S, Dispatch<SetStateAction<S>>] {
const hook = mountWorkInProgressHook();
if (typeof initialState === 'function') {
initialState = initialState();
}
hook.memoizedState = hook.baseState = initialState;
const queue: UpdateQueue<S> = {
pending: null,
lanes: NoLanes,
dispatch: null,
lastRenderedReducer: basicStateReducer,
lastRenderedState: initialState,
};
hook.queue = queue;
const dispatch = (queue.dispatch = dispatchSetState.bind(
null,
currentlyRenderingFiber,
queue
));
return [hook.memoizedState, dispatch];
}
function updateState<S>(initialState: S): [S, Dispatch<SetStateAction<S>>] {
return updateReducer(basicStateReducer, initialState);
}
function updateReducer<S, A>(
reducer: (S, A) => S,
initialArg: S
): [S, Dispatch<A>] {
const hook = updateWorkInProgressHook();
const queue = hook.queue;
// Process pending updates
const pending = queue.pending;
let newState = hook.baseState;
if (pending !== null) {
queue.pending = null;
let update = pending.next;
do {
const action = update.action;
newState = reducer(newState, action);
update = update.next;
} while (update !== pending.next);
}
hook.memoizedState = newState;
return [hook.memoizedState, queue.dispatch];
}
useEffect Implementation
function mountEffect(
create: () => (() => void) | void,
deps: Array<mixed> | void | null
): void {
return mountEffectImpl(
PassiveEffect | PassiveStaticEffect,
HookPassive,
create,
deps
);
}
function mountEffectImpl(fiberFlags, hookFlags, create, deps) {
const hook = mountWorkInProgressHook();
const nextDeps = deps === undefined ? null : deps;
currentlyRenderingFiber.flags |= fiberFlags;
hook.memoizedState = pushEffect(
HookHasEffect | hookFlags,
create,
undefined, // destroy function (set after running)
nextDeps
);
}
function updateEffect(
create: () => (() => void) | void,
deps: Array<mixed> | void | null
): void {
return updateEffectImpl(PassiveEffect, HookPassive, create, deps);
}
function updateEffectImpl(fiberFlags, hookFlags, create, deps) {
const hook = updateWorkInProgressHook();
const nextDeps = deps === undefined ? null : deps;
let destroy = undefined;
if (currentHook !== null) {
const prevEffect = currentHook.memoizedState;
destroy = prevEffect.destroy;
if (nextDeps !== null) {
const prevDeps = prevEffect.deps;
// Shallow compare deps
if (areHookInputsEqual(nextDeps, prevDeps)) {
// Deps unchanged, don't run effect
hook.memoizedState = pushEffect(hookFlags, create, destroy, nextDeps);
return;
}
}
}
// Deps changed, schedule effect
currentlyRenderingFiber.flags |= fiberFlags;
hook.memoizedState = pushEffect(
HookHasEffect | hookFlags,
create,
destroy,
nextDeps
);
}
Hook Effect Chain
┌─────────────────────────────────────────────────────────────────┐
│ Effect Chain on Fiber │
├─────────────────────────────────────────────────────────────────┤
│ │
│ fiber.updateQueue = { │
│ lastEffect: ─────────────────────────────────────┐ │
│ } │ │
│ │ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────▼───┐ │
│ │ Effect 1 │───►│ Effect 2 │───►│ Effect 3 │ │
│ │ useEffect() │ │ useLayout..()│ │ useEffect() │ │
│ │ tag: Passive │ │ tag: Layout │ │ tag: Passive │ │
│ │ create: fn │ │ create: fn │ │ create: fn │ │
│ │ destroy: fn │ │ destroy: fn │ │ destroy: fn │ │
│ │ deps: [a,b] │ │ deps: [c] │ │ deps: [] │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
│ ▲ │ │
│ └────────────── (circular) ──────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
Concurrent Features Deep Dive
Time Slicing
function workLoopConcurrent() {
while (workInProgress !== null && !shouldYield()) {
performUnitOfWork(workInProgress);
}
}
// shouldYield checks remaining time in current time slice
// Default: 5ms per slice
// Timeline:
// |--5ms--|--yield--|--5ms--|--yield--|--5ms--|--done--|
// | work | browser | work | browser | work | |
// | | paint | | input | | |
Interruption and Restart
┌─────────────────────────────────────────────────────────────────┐
│ Concurrent Interruption │
├─────────────────────────────────────────────────────────────────┤
│ │
│ Time ────────────────────────────────────────────────────► │
│ │
│ Low Priority Render: │
│ ┌──────────────────────────────────┐ │
│ │ A ─► B ─► C ─► D ─► ... │ (in progress) │
│ └──────────────────────────────────┘ │
│ │ │
│ │ User clicks! (High priority event) │
│ ▼ │
│ ┌───────────────┐ │
│ │ INTERRUPT │ │
│ │ Save progress │ │
│ └───────┬───────┘ │
│ │ │
│ ▼ │
│ High Priority Render: │
│ ┌──────────────────┐ │
│ │ Click Handler │ (runs to completion) │
│ └────────┬─────────┘ │
│ │ │
│ ▼ │
│ Resume Low Priority: │
│ ┌──────────────────────────────────────┐ │
│ │ ...D ─► E ─► F ─► complete │ │
│ └──────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
Lane-Based Rendering
function ensureRootIsScheduled(root: FiberRoot) {
const nextLanes = getNextLanes(root, NoLanes);
if (nextLanes === NoLanes) {
return; // Nothing to do
}
const newCallbackPriority = getHighestPriorityLane(nextLanes);
const existingCallbackPriority = root.callbackPriority;
if (existingCallbackPriority === newCallbackPriority) {
// Same priority, reuse existing task
return;
}
if (existingCallbackPriority > newCallbackPriority) {
// Higher priority work arrived, cancel existing
cancelCallback(existingCallbackNode);
}
// Schedule new task
let newCallbackNode;
if (newCallbackPriority === SyncLane) {
// Synchronous work
scheduleSyncCallback(performSyncWorkOnRoot.bind(null, root));
newCallbackNode = null;
} else {
// Concurrent work
const schedulerPriority = lanesToSchedulerPriority(nextLanes);
newCallbackNode = scheduleCallback(
schedulerPriority,
performConcurrentWorkOnRoot.bind(null, root)
);
}
root.callbackPriority = newCallbackPriority;
root.callbackNode = newCallbackNode;
}
Suspense Internals
Suspense Fiber Structure
// When a component suspends:
// throw promise;
// Suspense boundary catches it:
function updateSuspenseComponent(current, workInProgress, renderLanes) {
const nextProps = workInProgress.pendingProps;
let showFallback = false;
const didSuspend = (workInProgress.flags & DidCapture) !== NoFlags;
if (didSuspend) {
showFallback = true;
workInProgress.flags &= ~DidCapture;
}
const nextPrimaryChildren = nextProps.children;
const nextFallbackChildren = nextProps.fallback;
if (showFallback) {
// Mount fallback, hide primary
const fallbackFragment = mountSuspenseFallbackChildren(
workInProgress,
nextPrimaryChildren,
nextFallbackChildren,
renderLanes
);
workInProgress.child.memoizedState = { dehydrated: null, retryLane: NoLane };
return fallbackFragment;
} else {
// Mount primary
return mountSuspensePrimaryChildren(
workInProgress,
nextPrimaryChildren,
renderLanes
);
}
}
Suspense State Machine
┌─────────────────────────────────────────────────────────────────┐
│ Suspense States │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────┐ │
│ │ INITIAL │ │
│ │ (Render │ │
│ │ Primary) │ │
│ └──────┬──────┘ │
│ │ │
│ ┌──────────────┴──────────────┐ │
│ │ │ │
│ No Suspend Throws Promise │
│ │ │ │
│ ▼ ▼ │
│ ┌───────────────┐ ┌───────────────┐ │
│ │ COMPLETE │ │ SUSPENDED │ │
│ │ (Primary │ │ (Fallback │ │
│ │ Visible) │ │ Visible) │ │
│ └───────────────┘ └───────┬───────┘ │
│ │ │
│ Promise Resolves │
│ │ │
│ ▼ │
│ ┌───────────────┐ │
│ │ RETRY │ │
│ │ (Re-render │ │
│ │ Primary) │ │
│ └───────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
Promise Tracking and Retry
function throwException(
root: FiberRoot,
returnFiber: Fiber,
sourceFiber: Fiber,
value: mixed, // The thrown promise
rootRenderLanes: Lanes
) {
// Mark fiber as incomplete
sourceFiber.flags |= Incomplete;
if (
value !== null &&
typeof value === 'object' &&
typeof value.then === 'function'
) {
// It's a thenable (Promise)
const wakeable: Wakeable = value;
// Find nearest Suspense boundary
const suspenseBoundary = getNearestSuspenseBoundaryToCapture(returnFiber);
if (suspenseBoundary !== null) {
suspenseBoundary.flags |= ShouldCapture;
// Attach retry listener to promise
const retry = resolveRetryWakeable.bind(null, suspenseBoundary, wakeable);
wakeable.then(retry, retry);
return;
}
}
// No Suspense boundary, throw error
throw value;
}
Transitions and useDeferredValue
startTransition Implementation
function startTransition(scope: () => void) {
const prevTransition = ReactCurrentBatchConfig.transition;
// Mark as transition
ReactCurrentBatchConfig.transition = {};
try {
scope(); // Updates inside will use TransitionLanes
} finally {
ReactCurrentBatchConfig.transition = prevTransition;
}
}
// In scheduleUpdateOnFiber:
function requestUpdateLane(fiber: Fiber): Lane {
const transition = ReactCurrentBatchConfig.transition;
if (transition !== null) {
// Transition update gets low priority
return requestTransitionLane();
}
// Regular update
return getCurrentEventPriority();
}
useDeferredValue Implementation
function useDeferredValue<T>(value: T): T {
const [prevValue, setValue] = useState(value);
const [deferredValue, setDeferredValue] = useState(value);
useEffect(() => {
// Schedule deferred update
startTransition(() => {
setDeferredValue(value);
});
}, [value]);
// Return stale value during transition
return deferredValue;
}
// Actual implementation uses internal hooks:
function mountDeferredValue<T>(value: T): T {
const hook = mountWorkInProgressHook();
hook.memoizedState = value;
return value;
}
function updateDeferredValue<T>(value: T): T {
const hook = updateWorkInProgressHook();
const prevValue = hook.memoizedState;
return updateDeferredValueImpl(hook, prevValue, value);
}
function updateDeferredValueImpl<T>(hook: Hook, prevValue: T, value: T): T {
if (Object.is(prevValue, value)) {
return value;
}
const shouldDeferValue = !includesOnlyNonUrgentLanes(renderLanes);
if (shouldDeferValue) {
// Return previous value, schedule deferred update
scheduleUpdateOnFiber(
currentlyRenderingFiber,
TransitionLane,
eventTime
);
return prevValue;
}
hook.memoizedState = value;
return value;
}
Error Boundaries and Recovery
Error Handling Flow
function handleError(root, thrownValue) {
let erroredWork = workInProgress;
while (true) {
try {
// Try to complete remaining work
completeUnitOfWork(erroredWork);
break;
} catch (yetAnotherThrownValue) {
thrownValue = yetAnotherThrownValue;
erroredWork = workInProgress;
}
}
}
function throwException(
root: FiberRoot,
returnFiber: Fiber,
sourceFiber: Fiber,
value: mixed,
rootRenderLanes: Lanes
) {
sourceFiber.flags |= Incomplete;
// Walk up to find error boundary
let workInProgress = returnFiber;
while (workInProgress !== null) {
switch (workInProgress.tag) {
case ClassComponent: {
const instance = workInProgress.stateNode;
if (
typeof instance.getDerivedStateFromError === 'function' ||
typeof instance.componentDidCatch === 'function'
) {
// Found error boundary
const errorInfo = createCapturedValueAtFiber(value, sourceFiber);
// Queue error update
const update = createClassErrorUpdate(
workInProgress,
errorInfo,
rootRenderLanes
);
enqueueCapturedUpdate(workInProgress, update);
workInProgress.flags |= ShouldCapture;
return;
}
break;
}
}
workInProgress = workInProgress.return;
}
}
Error Boundary Component
class ErrorBoundary extends React.Component {
state = { hasError: false, error: null };
// Called during render phase
static getDerivedStateFromError(error) {
return { hasError: true, error };
}
// Called during commit phase
componentDidCatch(error, errorInfo) {
logErrorToService(error, errorInfo.componentStack);
}
render() {
if (this.state.hasError) {
return this.props.fallback;
}
return this.props.children;
}
}
Profiler and DevTools Integration
Profiler Fiber
// <Profiler id="App" onRender={callback}>
// <App />
// </Profiler>
function updateProfiler(current, workInProgress, renderLanes) {
if (enableProfilerTimer) {
workInProgress.flags |= Update;
// Record start time
workInProgress.actualStartTime = now();
}
const nextProps = workInProgress.pendingProps;
const nextChildren = nextProps.children;
reconcileChildren(current, workInProgress, nextChildren, renderLanes);
return workInProgress.child;
}
function completeProfilerComponent(workInProgress) {
if (enableProfilerTimer) {
// Calculate duration
const actualDuration = now() - workInProgress.actualStartTime;
workInProgress.actualDuration = actualDuration;
// Add to tree duration
workInProgress.treeBaseDuration = sumTreeDurations(workInProgress);
}
}
// onRender callback signature:
function onRenderCallback(
id, // Profiler id
phase, // "mount" | "update"
actualDuration, // Time spent rendering
baseDuration, // Estimated time without memoization
startTime, // When React started rendering
commitTime, // When React committed
interactions // Set of interactions being traced
) {}
DevTools Hook
// Source: packages/react-reconciler/src/ReactFiberDevToolsHook.js
export function injectInternals(internals) {
if (typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ === 'undefined') {
return false;
}
const hook = __REACT_DEVTOOLS_GLOBAL_HOOK__;
try {
rendererID = hook.inject({
bundleType: __DEV__ ? 1 : 0,
version: ReactVersion,
rendererPackageName: 'react-dom',
currentDispatcherRef: ReactCurrentDispatcher,
findFiberByHostInstance: getClosestInstanceFromNode,
reconcilerVersion: ReactVersion,
// Methods DevTools calls:
overrideHookState,
overrideProps,
setSuspenseHandler,
scheduleUpdate,
});
} catch (err) {}
return true;
}
Performance Patterns
Bailout Conditions
// Fiber bails out (skips re-rendering) when:
// 1. Props reference equality (same object)
if (oldProps === newProps) {
// Likely bailout if no state/context change
}
// 2. React.memo with shallow equality
const MemoComponent = React.memo(function(props) {
return <div>{props.value}</div>;
});
// 3. shouldComponentUpdate returns false
class MyComponent extends React.Component {
shouldComponentUpdate(nextProps, nextState) {
return nextProps.id !== this.props.id;
}
}
// 4. PureComponent (shallow comparison)
class MyPureComponent extends React.PureComponent {}
// 5. useMemo dependencies unchanged
const memoized = useMemo(() => expensive(a, b), [a, b]);
Keys and Reconciliation Cost
┌─────────────────────────────────────────────────────────────────┐
│ Key Impact on Reconciliation │
├─────────────────────────────────────────────────────────────────┤
│ │
│ WITHOUT KEYS (or index keys): │
│ │
│ Old: [A, B, C] │
│ New: [X, A, B, C] │
│ │
│ React sees: │
│ index 0: A → X (UPDATE A to X) ← wasteful │
│ index 1: B → A (UPDATE B to A) ← wasteful │
│ index 2: C → B (UPDATE C to B) ← wasteful │
│ index 3: - → C (INSERT C) │
│ │
│ WITH STABLE KEYS: │
│ │
│ Old: [A:a, B:b, C:c] │
│ New: [X:x, A:a, B:b, C:c] │
│ │
│ React sees: │
│ key 'x': INSERT X │
│ key 'a': KEEP A (maybe move) │
│ key 'b': KEEP B (maybe move) │
│ key 'c': KEEP C (maybe move) │
│ │
└─────────────────────────────────────────────────────────────────┘
Source Code Navigation Guide
Key Files by Topic
packages/react-reconciler/
├── src/
│ ├── ReactFiber.js # Fiber creation
│ ├── ReactFiberWorkLoop.js # Main work loop
│ ├── ReactFiberBeginWork.js # beginWork (render phase)
│ ├── ReactFiberCompleteWork.js # completeWork
│ ├── ReactFiberCommitWork.js # Commit phase
│ ├── ReactChildFiber.js # Child reconciliation
│ ├── ReactFiberHooks.js # Hooks implementation
│ ├── ReactFiberLane.js # Priority lanes
│ ├── ReactFiberSuspenseComponent.js # Suspense
│ ├── ReactFiberFlags.js # Effect flags
│ └── ReactFiberReconciler.js # Public API
packages/scheduler/
├── src/
│ ├── Scheduler.js # Task scheduling
│ ├── SchedulerMinHeap.js # Priority queue
│ └── SchedulerPriorities.js # Priority levels
packages/react-dom/
├── src/
│ ├── client/
│ │ ├── ReactDOMRoot.js # createRoot
│ │ └── ReactDOMHostConfig.js # DOM operations
│ └── events/
│ └── ReactDOMEventListener.js # Event system
Debugging Tips
// Enable React's internal debug logging
// In node_modules/react-dom/cjs/react-dom.development.js
// Add at top:
const debugLogging = true;
// In workLoopConcurrent:
if (debugLogging) {
console.log('Processing:', workInProgress?.type?.name || workInProgress?.tag);
}
// Using React DevTools Profiler:
// 1. Open DevTools → Profiler tab
// 2. Record a session
// 3. Analyze flame graph for expensive commits
// 4. Check "Why did this render?" for each component
// Using Scheduler tracing (experimental):
import { unstable_trace as trace } from 'scheduler/tracing';
trace('Button Click', performance.now(), () => {
setState(newValue);
});
Summary
React Fiber is a sophisticated scheduling and reconciliation architecture that transforms React from a synchronous, blocking renderer into an interruptible, prioritized rendering engine.
┌─────────────────────────────────────────────────────────────────┐
│ Fiber Architecture Summary │
├─────────────────────────────────────────────────────────────────┤
│ │
│ FIBER NODE │
│ • Virtual stack frame on the heap │
│ • Contains component type, state, props, effects │
│ • Linked via child/sibling/return pointers │
│ │
│ DOUBLE BUFFERING │
│ • Current tree (on screen) + WorkInProgress tree (building) │
│ • Trees swap on commit │
│ • Enables consistent UI updates │
│ │
│ WORK LOOP │
│ • Iterative traversal (not recursive) │
│ • Can yield to browser (shouldYield) │
│ • Time-sliced in 5ms chunks │
│ │
│ LANES │
│ • Bitmask-based priority system │
│ • Enables concurrent rendering │
│ • Updates batched by priority │
│ │
│ PHASES │
│ • Render: Pure, interruptible, builds WIP tree │
│ • Commit: Synchronous, applies mutations to DOM │
│ │
│ EFFECTS │
│ • Tracked via flags bitmask │
│ • Bubbled up via subtreeFlags │
│ • Applied during commit phase │
│ │
└─────────────────────────────────────────────────────────────────┘
Understanding Fiber internals enables you to:
- Write more performant React applications
- Debug rendering issues effectively
- Make informed architectural decisions
- Contribute to React's ecosystem
References
- React Source Code
- React Fiber Architecture (acdlite)
- A Cartoon Intro to Fiber (Lin Clark)
- Inside Fiber: In-depth overview (Maxim Koretskyi)
- React as a UI Runtime (Dan Abramov)
This document represents React 18+ architecture. Implementation details may vary across versions.
What did you think?