Adetayo Akinsanya unkletayo.dev

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:

  1. Current Tree: Represents the UI currently painted on screen.
  2. 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)
  1. Render Phase: Traverses the Fiber tree using beginWork() (moving down to children) and completeWork() (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.
  2. Commit Phase: Executes synchronously in a single uninterrupted tick. The reconciler mutates the real DOM in commitMutationEffects() and updates alternate pointers, 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 SyncLane priority (immediate typing feedback).
  • Re-filtering the 5,000-item table should run at TransitionLane priority.
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

  1. React Architecture Architecture. React Fiber Architecture Design Document. Written by Andrew Clark. GitHub.
  2. React Official Docs. Concurrent React and Priority Scheduling. React Core Team.
  3. W3C. Cooperative Scheduling with requestIdleCallback. W3C Standard.

Up Next in Series →

Part 6: State Management Architecture: Normalized Stores, Signals, XState Automata, and Immutability

Continue to Part 6 →