React 101 ⚛️ React Fiber Architecture and Reconciliation Algorithm
React Fiber is not a feature that application developers interact with directly. It is the internal engine that determines how and when React renders. Before Fiber, React used a recursive reconciler that walked the component tree synchronously. Once rendering began, it could not be interrupted. A deeply nested component tree or an expensive render could block the main thread for hundreds of milliseconds, dropping frames and making the UI unresponsive . Fiber replaced that recursive model with a cooperative, interruptible work loop. Each component is represented as a heap-allocated object—a Fiber node—rather than a transient stack frame. This allows React to pause work, yield to the browser, and resume later, which is the foundation for concurrent rendering and priority-based scheduling .
Key point: Fiber does not change what React renders; it changes how React schedules rendering. The reconciliation algorithm still compares trees and determines what changed, but Fiber breaks that work into units that can be interrupted and reprioritized.
Why Fiber exists
The call stack limitation. The old reconciler used JavaScript’s call stack to traverse the component tree recursively. The call stack is a last-in-first-out structure that executes until it is empty. It cannot be paused mid-traversal and resumed later . If a component tree is deep, the stack frames accumulate, and the browser cannot process user input, animations, or layout until the entire traversal completes.
The frame budget problem. Browsers refresh the screen at 60 frames per second, which means each frame has approximately 16 milliseconds of budget. If React’s rendering work exceeds that budget, the browser drops a frame, and the UI appears janky . A recursive traversal that takes 100 milliseconds blocks six frames. Fiber exists to keep rendering work within the frame budget by splitting it into smaller units.
The priority problem. Not all updates are equally urgent. A keystroke in a text input must be reflected immediately, while a background data refresh can wait. The old reconciler treated every update the same: once it started, it finished. Fiber assigns priorities to different types of work and allows a high-priority update to interrupt a lower-priority one . This is the mechanism that makes concurrent features like transitions and deferred values possible.
a. The Fiber Node and the Two Trees
A Fiber node is a JavaScript object that represents a unit of work. It contains the component’s type, its pending and memoized props, its state, links to its child, sibling, and parent nodes, and flags that mark side effects . The child pointer points to the first child, the sibling pointer points to the next sibling, and the return pointer points to the parent. This linked-list structure replaces the call stack’s implicit parent-child relationship with explicit pointers that React can traverse manually .
React maintains two Fiber trees at all times. The current tree represents the UI that is currently displayed on the screen. The work-in-progress tree is the next state being computed. React builds the work-in-progress tree while leaving the current tree untouched . If a higher-priority update arrives during this work, React can discard the work-in-progress tree and start over without affecting the visible UI. This is the mechanism that makes rendering interruptible without causing visual inconsistency.
// Simplified Fiber node structure
{
tag, // Component type (Function, Class, Host, etc.)
type, // Component constructor or DOM tag
stateNode, // DOM node or class instance
pendingProps, // New props
memoizedProps, // Previous props
memoizedState, // Current state
child, // First child fiber
sibling, // Next sibling fiber
return, // Parent fiber
alternate, // Link to the other tree (current ↔ work-in-progress)
flags, // Side effects to perform
lanes // Priority of this update
}
The alternate pointer is what connects the current and work-in-progress trees. Each Fiber node in one tree has a corresponding node in the other tree, linked by alternate. When the work-in-progress tree is complete and committed, React swaps the two by changing the root pointer. The work-in-progress tree becomes the current tree, and the old current tree becomes the alternate for the next update .
b. The Two Phases: Render and Commit
Fiber divides rendering into two distinct phases with different interruptibility guarantees.
The render phase is where React builds the work-in-progress tree. It traverses the Fiber tree, calls component functions, compares the new element tree to the previous one, and marks fibers with side-effect flags. This phase is interruptible. After processing each Fiber unit, React checks whether it still has time remaining in the current frame. If not, it yields to the browser and resumes later . If a higher-priority update arrives, React can abandon the current work-in-progress tree entirely.
// Simplified work loop
function workLoop() {
while (workInProgress !== null && !shouldYield()) {
workInProgress = performUnitOfWork(workInProgress);
}
// If shouldYield() returns true, React yields and resumes later
}
The commit phase is where React applies the changes to the DOM. This phase is uninterruptible and atomic . Once React begins committing, it applies all DOM mutations, runs layout effects, and updates refs in a single synchronous pass. Partial commits would leave the UI in an inconsistent state, so the commit phase cannot be interrupted. The interruptibility of the render phase is what allows React to keep the main thread responsive; the atomicity of the commit phase is what guarantees visual consistency.
c. Priority and Lanes
Fiber assigns priorities to updates using a mechanism called lanes. A lane is a bitmask that represents a priority level. Higher-priority updates occupy different lanes than lower-priority ones . When React processes the work-in-progress tree, it can skip over fibers that have no work in the current priority lane, which avoids traversing subtrees that are not relevant to the urgent update.
The priority system is what makes concurrent features work. A user typing in an input produces a high-priority update that should be reflected immediately. A list filtering operation triggered by that input can be assigned a lower priority and processed in the background . If the user types again before the filter completes, React can interrupt the filter, process the new keystroke, and restart the filter with the new value. Without priority lanes, every update would be synchronous and blocking.
Complete Example Session
// ============================================
// PART 1: OLD RECONCILER — RECURSIVE TRAVERSAL
// ============================================
// The stack reconciler walked the tree recursively
// Once it started, it could not be interrupted
function oldReconcile(element) {
// Process this element
// Recurse into children
element.children.forEach(child => oldReconcile(child));
// No yield point; blocks until complete
}
// ============================================
// PART 2: FIBER NODE — UNIT OF WORK
// ============================================
// Each component becomes a Fiber node
// Links to child, sibling, and return (parent)
const fiberNode = {
tag: 'FunctionComponent',
type: MyComponent,
pendingProps: { value: 1 },
memoizedProps: { value: 0 },
child: null,
sibling: null,
return: parentFiber,
alternate: correspondingNodeInOtherTree,
flags: 'Update',
lanes: 'DefaultLane'
};
// ============================================
// PART 3: WORK LOOP — INTERRUPTIBLE TRAVERSAL
// ============================================
function workLoop(deadline) {
while (workInProgress !== null && deadline.timeRemaining() > 0) {
workInProgress = performUnitOfWork(workInProgress);
}
// If deadline.timeRemaining() <= 0, yield to browser
// Resume on next idle callback or frame
}
// ============================================
// PART 4: PERFORM UNIT OF WORK — DEPTH-FIRST
// ============================================
function performUnitOfWork(fiber) {
// Begin work: process this fiber, create children
const next = beginWork(fiber);
if (next !== null) {
// Has children: descend
return next;
}
// No children: complete this fiber and ascend
let completed = fiber;
while (completed !== null) {
completeWork(completed);
if (completed.sibling !== null) {
// Has sibling: process it
return completed.sibling;
}
// No sibling: ascend to parent
completed = completed.return;
}
return null; // Root completed
}
// ============================================
// PART 5: BEGIN WORK — RECONCILE CHILDREN
// ============================================
// Compare new element to existing fiber
// Determine whether to reuse, update, or replace
function beginWork(fiber) {
// Call component function or update DOM
// Reconcile children based on element tree
// Return first child fiber to process
return fiber.child;
}
// ============================================
// PART 6: COMPLETE WORK — BUILD EFFECT LIST
// ============================================
// Mark side effects (DOM updates, refs, lifecycle)
// Add fiber to effect list for commit phase
function completeWork(fiber) {
// Collect effects into a linked list
// Commit phase will traverse this list
}
// ============================================
// PART 7: RENDER PHASE — INTERRUPTIBLE
// ============================================
// Build work-in-progress tree
// Check time remaining after each fiber
// If no time, yield and resume later
// If high-priority update arrives, discard and restart
// ============================================
// PART 8: COMMIT PHASE — ATOMIC
// ============================================
// Apply all DOM mutations in one pass
// Run layout effects
// Update refs
// Swap current and work-in-progress trees
function commitRoot(root) {
// Traverse effect list
// Apply DOM changes
// Update current tree pointer
}
// ============================================
// PART 9: LANES — PRIORITY ASSIGNMENT
// ============================================
// User input: high priority
// Data fetch: lower priority
// Transition: interruptible
// Each lane is a bitmask
const HighPriority = 0b0000000001;
const DefaultPriority = 0b0000000100;
const LowPriority = 0b0000010000;
// ============================================
// PART 10: DOUBLE BUFFERING — CURRENT AND WORK-IN-PROGRESS
// ============================================
// current tree: displayed UI
// work-in-progress: next UI being built
// alternate: pointer between them
// On commit: workInProgress becomes current
The ten parts trace the Fiber work loop from its recursive predecessor through the node structure, the interruptible traversal, and the two-phase commit. Part 7 is the critical distinction: the render phase yields to the browser, while Part 8 shows the commit phase that must complete without interruption.
Quick Reference
Fiber Concepts
| Concept | Purpose |
|---|---|
| Fiber node | Unit of work; heap-allocated object |
| Current tree | The UI currently displayed |
| Work-in-progress tree | The next UI being computed |
| Alternate | Link between current and work-in-progress nodes |
| Render phase | Builds the tree; interruptible |
| Commit phase | Applies DOM changes; atomic |
| Lanes | Bitmask priority levels |
Old vs. Fiber Reconciler
| Aspect | Stack Reconciler | Fiber Reconciler |
|---|---|---|
| Traversal | Recursive | Linked-list work loop |
| Interruptible | No | Yes (render phase) |
| Priority | None | Lanes |
| Work unit | Call stack frame | Fiber node |
| Concurrent features | Impossible | Enabled |
Phase Guarantees
| Phase | Interruptible | Side Effects |
|---|---|---|
| Render | Yes | None (pure) |
| Commit | No | DOM mutations, effects |
Best Practices
✅ Do This:
// Keep render phase pure; no side effects
function Component() {
const data = useMemo(() => compute(), []); // ✅
return <div>{data}</div>;
}
// Use stable keys for list reconciliation
items.map(item => <li key={item.id}>{item.name}</li>); // ✅
// Use transitions for non-urgent updates
const [isPending, startTransition] = useTransition(); // ✅
❌ Don’t Do This:
// Perform DOM mutations during render
function Component() {
document.title = 'Title'; // Side effect in render // ❌
}
// Use array index as key when list order changes
items.map((item, i) => <li key={i}>{item.name}</li>); // ❌
// Block the render phase with expensive synchronous work
const data = heavySyncComputation(); // ❌
Common Pitfalls
| Pitfall | Why It Happens | Fix |
|---|---|---|
| Janky animation during data update | Low-priority work blocks high-priority | Use useTransition or useDeferredValue |
| Component re-renders unexpectedly | Fiber revisits subtree due to unstable props | Memoize props; use stable references |
| Side effect runs twice in development | Render phase may repeat | Move side effects to useEffect |
| List reorder causes DOM churn | Unstable keys defeat reconciliation | Use stable unique key |
Real-World Examples
1. Fiber Node Structure
{ tag, type, stateNode, child, sibling, return, alternate, flags, lanes }
2. Work Loop
while (workInProgress !== null && !shouldYield()) {
workInProgress = performUnitOfWork(workInProgress);
}
3. Render Phase Yield
if (deadline.timeRemaining() <= 0) return; // Yield to browser
4. Commit Phase Atomic
// All DOM updates applied synchronously
commitRoot(root);
5. Double Buffering
// current tree and work-in-progress tree linked by alternate
fiber.alternate = correspondingNode;
6. Priority Lanes
// High priority: user input
// Default priority: normal updates
// Low priority: background work
7. beginWork
function beginWork(fiber) {
// Reconcile children, return first child
}
8. completeWork
function completeWork(fiber) {
// Mark effects, add to effect list
}
9. Tree Swap on Commit
// After commit: root.current = finishedWork
10. Interruptible Render
// If higher priority arrives, discard workInProgress and restart
Visual
The Old Recursive Reconciler
┌──────────────────────────────────────────────────────────────┐
│ STACK RECONCILER │
│ │
│ Render App │
│ └── Render Header │
│ └── Render Nav │
│ └── Render Main │
│ └── Render List │
│ └── Render Item 1 │
│ └── Render Item 2 │
│ │
│ Call stack grows. No yield point. │
│ Browser cannot respond until entire tree renders. │
│ │
└──────────────────────────────────────────────────────────────┘
The Fiber Work Loop
┌──────────────────────────────────────────────────────────────┐
│ FIBER WORK LOOP │
│ │
│ ┌─────────────────┐ │
│ │ Start at root │ │
│ └────────┬────────┘ │
│ ▼ │
│ ┌─────────────────┐ │
│ │ beginWork │ Process fiber, create children │
│ └────────┬────────┘ │
│ ▼ │
│ ┌─────────────────┐ │
│ │ Has child? │────► YES: descend to child │
│ └────────┬────────┘ │
│ │ NO │
│ ▼ │
│ ┌─────────────────┐ │
│ │ completeWork │ Mark effects │
│ └────────┬────────┘ │
│ ▼ │
│ ┌─────────────────┐ │
│ │ Has sibling? │────► YES: process sibling │
│ └────────┬────────┘ │
│ │ NO │
│ ▼ │
│ ┌─────────────────┐ │
│ │ Ascend to parent│ │
│ └────────┬────────┘ │
│ ▼ │
│ ┌─────────────────┐ │
│ │ Time remaining? │────► NO: yield to browser │
│ └────────┬────────┘ │
│ │ YES │
│ ▼ │
│ ┌─────────────────┐ │
│ │ Next fiber │ │
│ └─────────────────┘ │
│ │
│ The loop checks time after each fiber. │
│ It can yield and resume without losing progress. │
│ │
└──────────────────────────────────────────────────────────────┘
Two Trees and Double Buffering
┌──────────────────────────────────────────────────────────────┐
│ CURRENT TREE (displayed) WORK-IN-PROGRESS (building) │
│ │
│ ┌─────────────────┐ ┌─────────────────┐ │
│ │ App │ │ App │ │
│ │ ├── Header │◄────────►│ ├── Header │ │
│ │ └── List │ alternate│ └── List │ │
│ │ ├── Item A │ │ ├── Item A │ │
│ │ └── Item B │ │ └── Item C │ │
│ └─────────────────┘ └─────────────────┘ │
│ │
│ User sees current. React builds work-in-progress. │
│ │
│ ┌─────────────────────────────────────────────────────────┐│
│ │ COMMIT: swap pointers ││
│ │ work-in-progress becomes current ││
│ └─────────────────────────────────────────────────────────┘│
│ │
│ If interrupted: discard work-in-progress, current intact. │
│ │
└──────────────────────────────────────────────────────────────┘
Render and Commit Phases
┌──────────────────────────────────────────────────────────────┐
│ RENDER PHASE (interruptible) │
│ │
│ ┌─────────────────────────────────────────────────────────┐│
│ │ Build work-in-progress tree ││
│ │ Call components, reconcile children ││
│ │ Mark effects ││
│ │ ││
│ │ Yield point: check time remaining ││
│ │ Interruptible: high-priority update can preempt ││
│ │ No DOM mutations ││
│ └─────────────────────────────────────────────────────────┘│
│ │ │
│ │ Tree complete │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────┐│
│ │ COMMIT PHASE (atomic) ││
│ │ ││
│ │ Apply all DOM mutations ││
│ │ Run layout effects ││
│ │ Update refs ││
│ │ Swap current and work-in-progress trees ││
│ │ ││
│ │ Uninterruptible: must complete without yielding ││
│ └─────────────────────────────────────────────────────────┘│
│ │
│ The render phase yields; the commit phase does not. │
│ This is the trade-off that makes Fiber both responsive │
│ and consistent. │
│ │
└──────────────────────────────────────────────────────────────┘
Summary
| Item | Value |
|---|---|
| Fiber | Reimplementation of the reconciler |
| Fiber node | Heap-allocated unit of work |
| Current tree | Displayed UI |
| Work-in-progress tree | Next UI being built |
| Alternate | Link between the two trees |
| Render phase | Interruptible; builds tree; no DOM changes |
| Commit phase | Atomic; applies DOM changes |
| Lanes | Priority bitmasks |
| Work loop | Yields based on time remaining |
| Double buffering | Swap trees on commit |
Key takeaways:
- Fiber replaces the call stack with a linked list of work units. The recursive reconciler could not be interrupted because the call stack executes until empty. Fiber nodes are heap-allocated objects with explicit
child,sibling, andreturnpointers, which allows React to traverse the tree manually and yield after any unit of work . - The render phase is interruptible; the commit phase is atomic. React builds the work-in-progress tree during the render phase, checking after each fiber whether it still has time in the frame. If not, it yields and resumes later. The commit phase applies all DOM mutations in one synchronous pass to avoid visual inconsistency .
- Double buffering isolates the visible UI from the work being computed. The current tree is never mutated during rendering. If a higher-priority update arrives, the work-in-progress tree can be discarded without affecting what the user sees .
- Lanes provide priority. Different types of updates are assigned to different lanes, and React processes the highest-priority lane first. This is what allows a keystroke to interrupt a background list filter without dropping the keystroke .
- Fiber is what makes concurrent features possible. Transitions, deferred values, and Suspense all depend on the ability to interrupt, pause, and restart rendering work. Without Fiber’s interruptible work loop, these features could not exist .
Remember: Fiber is not a feature you opt into; it is the engine underneath every React 16+ application. The reconciliation algorithm still compares trees and determines what changed, but Fiber changes how that comparison is scheduled. The work is broken into units, each unit is processed one at a time, and the work loop yields to the browser when the frame budget runs out. The visible result is that React can keep the UI responsive even when the component tree is large or the render is expensive. The trade-off is that the render phase may be repeated if it is interrupted, which is why render functions must be pure and side effects belong in the commit phase.
Stop using slow, ad-bloated tool sites! 🤮
🔎 Search “KandZ Tools” on Google to use many professional utilities for free.
KandZ.me is the ultimate minimalist hub for:
✅ Finance (Mortgage, Interest, Inflation)
✅ Tech (Base64, JSON, Dev Suite, IP)
✅ Health (BMI, BMR, TDEE)
✅ Productivity (Timer, Workspace, QR)
⚡️ Fast & Private
🔒 No data leaves your device
💎 100% Free
🔗 Use it now: https://tools.kandz.me
🔖 Bookmark it—you’ll need it later!