0.2 The four pillars in 10 minutes
The rest of this course goes deep — one mechanism at a time, each backed by real React source. Before you dive into any of them, it helps to have the whole map in your head, so every later lesson has a place to land. This lesson is that map. React's render engine stands on four pillars, and in ten minutes you'll be able to name all four and say what each one does.
Pillar 1 · Fibers — a to-do list shaped like a tree
When you write JSX you describe a tree of elements. React turns each element into a Fiber — a small object that is both the unit of work (one thing React has to process) and the unit of memory (where that piece of UI keeps its state, its effects, and its link to the real DOM). The Fibers link into a tree using just three pointers: .child for the first child, .sibling for the next sibling, and .return for the parent. React walks this tree, Fiber by Fiber, to get its work done. Proven later in Module 1.
FiberRootNode (NOT a fiber)
│ .containerInfo = <div id="root"> (real DOM)
│ .current
▼
HostRoot fiber (tag=3) ← top fiber, .stateNode → FiberRootNode
│ .child
▼
App fiber (tag=0, FunctionComponent) ← .return → HostRoot
│ .child
▼
Fragment fiber (tag=7) ← .return → App (the <> </>)
│ .child
▼
Header fiber (tag=0) ← .return → Fragment
│ .sibling
▼
Content fiber (tag=0) ← .return → Fragment (NOT child of Header!)
│ .child │ .sibling
▼ ▼
Sidebar fiber (tag=0) Footer fiber (tag=0) ← .return → Fragment
│ .sibling (sibling of Content, NOT its child)
▼
Main fiber (tag=0) ← .return → Content
.sibling = null
Pillar 2 · Two trees — a draft and what's on screen
React never edits the tree you're currently looking at. It keeps two copies: the current tree (what is on screen right now) and a work-in-progress tree (the draft it builds the next version on). The two are wired together fiber-for-fiber by the .alternate pointer. React does all its building on the draft, where a half-finished render can never be seen, and only at the very end does it swap the two so the draft becomes the new current. This is called double buffering. Proven later in Module 1 (lesson 1.3).
RENDER 1 RENDER 2
───────────────── ─────────────────
Elements: Created fresh ─────────────────► Created fresh (NEW objects)
{ type: 'div' } { type: 'div' }
{ type: Child } { type: Child }
│ │
▼ ▼
┌───────────┐ ┌───────────┐
│ current │◄── alternate ─────►│workInProg │
Fibers: │ (tree) │ │ (tree) │
└───────────┘ └───────────┘
│ │
│ After commit: │
▼ ▼ ▼
┌───────────┐ ┌───────────┐ ┌───────────┐
│ OLD │ │ current │◄──►│ OLD │
│(reusable) │ │ (tree) │ │(reusable) │
└───────────┘ └───────────┘ └───────────┘
SWAP! Fibers are REUSED, not recreated!
Pillar 3 · Lanes — which work is urgent
Not all updates are equally urgent. A click should feel instant; re-filtering a giant list in the background can wait. React records that urgency with Lanes. A lane is literally a single bit in a 32-bit number, and the bit's position is its priority — lower bits are more urgent. Because each lane is its own bit, React can pack many pending priorities into one integer and test or combine them with fast bitwise math. This lets React render the urgent input first and the slow list in a later pass, so typing never waits behind the list. Proven later in Module 3.
WITH Lanes:
T=0ms User types 'r'
T=1ms getNextLanes() → 0b1 (SyncLane — highest priority)
T=2ms Render Pass 1 (renderLanes = 0b1):
├─ Render <input> (1 ms)
└─ SKIP all <ResultItem>s (lane check fails)
T=3ms Commit — user sees 'r' and can type next character ✓
T=10ms Render Pass 2 (renderLanes = 0b1000000):
└─ Render 10,000 results (slow but input already updated)
T=500ms Commit results ✓
Pillar 4 · The Scheduler — when the work runs
Lanes decide what's urgent, but something still has to make sure a big render doesn't freeze the page while it runs. That's the Scheduler — think of it as a traffic light for JavaScript work. Its one job is to stop React from holding the main thread too long: work for about 5 ms, then hand control back to the browser so it can handle input and paint, then resume. It knows nothing about your components or which update matters — it only knows time. Proven later in Module 4.
Frame budget: 16.77ms (60fps) Yield interval: 5ms Yields per frame: 16.77 ÷ 5 ≈ 3.3 Frame 1 (0–16.77ms): T=0ms: React starts rendering T=5ms: Yield #1 → browser can handle input / paint T=5ms: React resumes T=10ms: Yield #2 → browser can handle input / paint T=10ms: React resumes T=15ms: Yield #3 → browser can handle input / paint T=15ms: React resumes T=16.77ms: Frame boundary → browser paints
That's the whole map. The four pillars work together: Fibers hold the work, the two trees keep half-built work hidden, Lanes mark what's urgent, and the Scheduler controls when it all runs. Everything else in this course is detail hung on these four.
See it in the source
You don't need to read these closely yet — just see that each pillar is a real, named thing in React's code. One short proof per pillar.
Pillar 1 — a Fiber links to its tree with three pointers
// ── Tree structure ──
this.return = null; // parent fiber
this.child = null; // first child
this.sibling = null; // next sibling
this.index = 0; // position among siblings
Show the full FiberNode object
function FiberNode(tag, pendingProps, key, mode) {
// ── Identity ──
this.tag = tag; // what KIND of fiber (0=FunctionComponent, 5=host DOM, 13=Suspense…)
this.key = key; // reconciliation key
this.elementType = null; // the element's type (your function/class, or 'div')
this.type = null; // resolved type
this.stateNode = null; // DOM node, or class instance, or FiberRootNode
// ── Tree structure ──
this.return = null; // parent fiber
this.child = null; // first child
this.sibling = null; // next sibling
this.index = 0; // position among siblings
// ── Ref ──
this.ref = null;
this.refCleanup = null;
// ── Props & state ──
this.pendingProps = pendingProps; // incoming props (the input to this render)
this.memoizedProps = null; // props from the LAST render (for comparison)
this.updateQueue = null; // queue of pending updates
this.memoizedState = null; // last state — the HOOK CHAIN for function components
this.dependencies = null; // context subscriptions ← powers useContext
// ── Mode ──
this.mode = mode; // Concurrent / Strict / etc.
// ── Effects ──
this.flags = 0; // side-effect flags on THIS fiber (Placement, Update, …)
this.subtreeFlags = 0; // OR of all descendants' flags (lets React skip clean subtrees)
this.deletions = null; // children scheduled for removal
// ── Lanes (scheduling) ──
this.lanes = 0; // work scheduled on THIS fiber
this.childLanes = 0; // work scheduled somewhere in its subtree
// ── Double buffering ──
this.alternate = null; // link to this fiber's other copy (current ↔ workInProgress)
}
Pillar 2 — the draft fiber is the current one's .alternate, reused not recreated
function createWorkInProgress(current, pendingProps) {
var workInProgress = current.alternate; // ← Check for existing fiber!
if (workInProgress === null) {
// First time — create new fiber
workInProgress = createFiber(current.tag, pendingProps, current.key, current.mode);
workInProgress.alternate = current;
current.alternate = workInProgress;
} else {
// REUSE existing fiber! Just update props
workInProgress.pendingProps = pendingProps;
workInProgress.type = current.type;
workInProgress.flags = NoFlags; // Reset flags
workInProgress.subtreeFlags = NoFlags;
workInProgress.deletions = null;
}
return workInProgress;
}
Pillar 3 — a lane really is one bit; its position is its priority
// 32-bit layout (bits 0–30 usable, bit 31 reserved):
//
// Bit 31 (reserved)
// │ Bit 30 (DeferredLane)
// │ │ Bits 27–29 (Idle/Offscreen)
// │ │ │ Bits 7–26 (Transition/Retry/Selective)
// │ │ │ │ Bits 0–6 (Sync/Input/Default)
// ▼ ▼ ▼ ▼ ▼
// [X][D][III][TTTTTTTTTTTTTTTTTTT][SSSSSSS]
SyncLane = 0b0000000000000000000000000000001; // bit 0 — most urgent
InputContinuousLane = 0b0000000000000000000000000000100; // bit 2 — dragging, scrolling
DefaultLane = 0b0000000000000000000000000010000; // bit 4 — normal setState
TransitionLane1 = 0b0000000000000000000000001000000; // bit 6 — startTransition work
IdleLane = 0b0100000000000000000000000000000; // bit 29 — least urgent
OffscreenLane = 0b1000000000000000000000000000000; // bit 30 — hidden/Activity content
Pillar 4 — the Scheduler yields after ~5 ms; it only knows time
// SchedulerFeatureFlags.js
export const frameYieldMs = 5; // Default yield interval
export const continuousYieldMs = 50; // For continuous input (mousemove)
export const maxYieldMs = 300; // Absolute maximum before forced yield
// shouldYieldToHost — shipped in React 18.3.1 (simple time-based check;
// the isInputPending branch is gated behind enableIsInputPending = false)
function shouldYieldToHost() {
const timeElapsed = getCurrentTime() - startTime;
if (timeElapsed < frameInterval) { // frameInterval = 5ms
return false;
}
return true;
}