⚛ The Course  /  Module 2 · One Pass of Work · 2.3
Module 2 · One Pass of Work

2.3 A full pass, visualized

You have learned the pieces one at a time: what a Fiber is, how Fibers link, the two trees, and the split between rendering and committing. This lesson does no new teaching — it puts those pieces side by side in two annotated maps so you can trace a single update from the work tree all the way to the painted screen, in one breath.

The work tree, with every pointer

In Module 1 you met the Fiber and its links. Here is the whole tree drawn at once, for a tiny app, with both trees present and every pointer labelled.

Read it slowly. The left column is the work-in-progress tree React is building; the right column is the current tree on screen. Each component has one fiber on each side, and the two are tied together by alternate — that pairing is double buffering from lesson 1.3.

╔══════════════════════════════════════════════════════════════════════════════╗
║                    FIBER TREE STRUCTURE – ALL POINTERS                       ║
╚══════════════════════════════════════════════════════════════════════════════╝

EXAMPLE JSX:
  <App>
    <Header />
    <Content>
      <Sidebar />
      <Main />
    </Content>
  </App>

FIBER TREE WITH POINTERS:

                              ┌──────────────────────┐
                              │     FiberRoot         │
                              │   (tag: HostRoot)     │
                              │                       │
                              │ current ──────────┐   │
                              │ finishedWork ─┐   │   │
                              └───────────────│───│───┘
                                              │   │
        ┌─────────────────────────────────────┘   │
        │ (workInProgress during render)           │
        ▼                                          ▼
  ┌───────────┐  alternate  ┌───────────┐
  │   App     │◄───────────►│   App     │
  │ (WIP)     │             │ (current) │
  └───────────┘             └───────────┘
        │ child                    │
        ▼                          ▼
  ┌───────────┐             ┌───────────┐
  │  Header   │             │  Header   │
  └───────────┘             └───────────┘
        │ sibling                  │
        ▼                          ▼
  ┌───────────┐             ┌───────────┐
  │  Content  │             │  Content  │
  └───────────┘             └───────────┘
        │ child                    │
        ▼                          ▼
  ┌───────────┐             ┌───────────┐
  │  Sidebar  │             │  Sidebar  │
  └───────────┘             └───────────┘
        │ sibling                  │
        ▼                          ▼
  ┌───────────┐             ┌───────────┐
  │   Main    │             │   Main    │
  └───────────┘             └───────────┘


POINTER EXPLANATIONS:

  ┌───────────────┬────────────────────────┬──────────────────────────────────┐
  │  POINTER      │  DIRECTION             │  PURPOSE                         │
  ├───────────────┼────────────────────────┼──────────────────────────────────┤
  │  child        │  Parent → First Child  │  First child fiber               │
  │  sibling      │  Fiber → Next Sibling  │  Next sibling in same parent     │
  │  return       │  Child → Parent        │  Parent fiber (for traversal)    │
  │  alternate    │  Current ↔ WIP         │  Double buffering link           │
  │  stateNode    │  Fiber → DOM/Instance  │  Actual DOM node or class inst.  │
  └───────────────┴────────────────────────┴──────────────────────────────────┘


TRAVERSAL PATTERN (performUnitOfWork):

  ┌─────────────────────────────────────────────────────────────────────────┐
  │                                                                         │
  │    1. Go DOWN via child      (beginWork – process fiber)                │
  │    2. Go RIGHT via sibling   (after completing subtree)                 │
  │    3. Go UP via return       (when no more siblings)                    │
  │                                                                         │
  │                      App                                                │
  │                       │                                                 │
  │            ┌──────────┴──────────┐                                      │
  │            ▼                     │                                      │
  │         Header ─────────────► Content                                   │
  │                                  │                                      │
  │                       ┌──────────┴──────────┐                           │
  │                       ▼                     ▼                           │
  │                    Sidebar ────────────► Main                           │
  │                                                                         │
  │  Visit order:    App → Header → Content → Sidebar → Main                │
  │  Complete order: Header → Sidebar → Main → Content → App                │
  │                                                                         │
  └─────────────────────────────────────────────────────────────────────────┘

Tie it back to what you just learned. The three vertical arrows on each side — child, sibling, then child again — are exactly the linked-list shape from lesson 1.2; a Fiber has no array of children, only a first child and a chain of siblings. The return pointer (not drawn as an arrow, but listed in the pointer table) is how a fiber climbs back to its parent.

The bottom box is lesson 2.1 in one picture. beginWork runs as React goes down via child — that is the Visit order. completeWork runs as React comes back up — that is the Complete order. The two orders are not the same, and that is the single most useful thing to remember from this diagram.

The whole pass, in five phases

Module 2 split a single pass into two halves: render (build in memory) and commit (touch the real DOM). This next map widens the lens to the full journey of an update — five phases, top to bottom.

╔══════════════════════════════════════════════════════════════════════════════╗
║                        REACT RENDER CYCLE – COMPLETE FLOW                   ║
╚══════════════════════════════════════════════════════════════════════════════╝

┌─────────────────────────────────────────────────────────────────────────────┐
│ PHASE 1: TRIGGER                                                            │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│   User Action              setState()              Context Change           │
│       │                       │                        │                   │
│       └───────────────────────┴────────────────────────┘                   │
│                               │                                             │
│                               ▼                                             │
│                    ┌─────────────────────┐                                  │
│                    │ dispatchSetState()  │                                  │
│                    └─────────────────────┘                                  │
│                               │                                             │
│                    ┌──────────┴──────────┐                                  │
│                    ▼                     ▼                                  │
│           ┌─────────────────┐   ┌────────────────────┐                     │
│           │ Assign Lane     │   │ Create Update      │                     │
│           │ (priority bit)  │   │ Object             │                     │
│           └─────────────────┘   └────────────────────┘                     │
│                    │                     │                                  │
│                    └──────────┬──────────┘                                  │
│                               ▼                                             │
│         ┌──────────────────────────────────────────┐                       │
│         │ enqueueConcurrentHookUpdate(...)  →       │  ← sets fiber.lanes   │
│         │   markUpdateLaneFromFiberToRoot           │    bubbles childLanes │
│         │   (bubble lanes up; RETURNS the root)     │    up to the root     │
│         └──────────────────────────────────────────┘                       │
│                               │ (returns root)                              │
│                               ▼                                             │
│              ┌──────────────────────────────────┐                          │
│              │ scheduleUpdateOnFiber(root,fiber, │                          │
│              │                       lane)       │                          │
│              │  • markRootUpdated(root, lane)    │  ← does NOT bubble lanes │
│              └──────────────────────────────────┘                          │
│                               │                                             │
│                               ▼                                             │
│                    ┌─────────────────────┐                                  │
│                    │ensureRootIsScheduled│  ← CALLED BY scheduleUpdateOnFiber│
│                    └─────────────────────┘                                  │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘
                                │
                                ▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ PHASE 2: SCHEDULE                                                           │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│                    ┌─────────────────────┐                                  │
│                    │   getNextLanes()    │                                  │
│                    │ (highest priority)  │                                  │
│                    └─────────────────────┘                                  │
│                               │                                             │
│               ┌───────────────┼───────────────┐                             │
│               ▼               ▼               ▼                             │
│        ┌───────────┐   ┌───────────┐   ┌───────────┐                       │
│        │ SyncLane  │   │ InputLane │   │ Transition│                       │
│        │ (0b0010)  │   │ (0b1000)  │   │ (0b10000) │                       │
│        └───────────┘   └───────────┘   └───────────┘                       │
│               │               │               │                             │
│               ▼               └───────┬───────┘                             │
│        ┌───────────┐                  ▼                                     │
│        │   SYNC    │           ┌───────────┐                                │
│        │  No yield │           │ Scheduler │                                │
│        │  Execute  │           │ Task Queue│                                │
│        │ immediate │           │ (yields)  │                                │
│        └───────────┘           └───────────┘                                │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘
                                │
                                ▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ PHASE 3: RENDER (In Memory – Interruptible)                                 │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│   ┌─────────────────────────────────────────────────────────────────────┐  │
│   │                        workLoopConcurrent                           │  │
│   │  ┌──────────────────────────────────────────────────────────────┐  │  │
│   │  │  while (workInProgress !== null && !shouldYield()) {         │  │  │
│   │  │      performUnitOfWork(workInProgress);                      │  │  │
│   │  │  }                                                           │  │  │
│   │  └──────────────────────────────────────────────────────────────┘  │  │
│   └─────────────────────────────────────────────────────────────────────┘  │
│                                                                             │
│   For each fiber:                                                           │
│   ┌─────────────────┐     ┌─────────────────┐     ┌─────────────────┐      │
│   │   beginWork()   │────►│ Process fiber   │────►│ completeWork()  │      │
│   │   (top-down)    │     │ Check bailout   │     │ (bottom-up)     │      │
│   └─────────────────┘     └─────────────────┘     └─────────────────┘      │
│          │                        │                       │                 │
│          ▼                        ▼                       ▼                 │
│   Create child fibers      Apply updates           Create DOM nodes         │
│   Check lanes              Run component           Diff props               │
│   Reconcile children       Create elements         Bubble flags             │
│                                                                             │
│   ┌─────────────────────────────────────────────────────────────────────┐  │
│   │  Interruption Points:                                               │  │
│   │  • shouldYield() = true → PAUSE, yield to browser                  │  │
│   │  • Higher priority arrives → ABANDON, start fresh                  │  │
│   └─────────────────────────────────────────────────────────────────────┘  │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘
                                │
                                ▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ PHASE 4: COMMIT (Synchronous – Uninterruptible)                             │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│   ┌─────────────────────────────────────────────────────────────────────┐  │
│   │  commitRoot(root, finishedWork)                                     │  │
│   │  • NO shouldYield() checks here!                                    │  │
│   │  • Must complete all DOM updates atomically                         │  │
│   └─────────────────────────────────────────────────────────────────────┘  │
│                                                                             │
│   ┌─────────────────┐     ┌─────────────────┐     ┌─────────────────┐      │
│   │ Before Mutation │────►│    Mutation     │────►│     Layout      │      │
│   │                 │     │                 │     │                 │      │
│   │ • Snapshot      │     │ • DOM updates   │     │ • useLayoutEffect│     │
│   │ • getSnapshot   │     │ • appendChild   │     │ • Ref attach    │      │
│   │                 │     │ • removeChild   │     │ • Measure DOM   │      │
│   └─────────────────┘     └─────────────────┘     └─────────────────┘      │
│                                   │                                         │
│                                   ▼                                         │
│                    ┌─────────────────────────┐                              │
│                    │ root.current = finished │  ← Atomic swap!              │
│                    └─────────────────────────┘                              │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘
                                │
                                ▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ PHASE 5: PASSIVE EFFECTS (Asynchronous)                                     │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│   ┌─────────────────────────────────────────────────────────────────────┐  │
│   │  Scheduled via Scheduler (after browser paint)                      │  │
│   │  • useEffect cleanup (previous)                                     │  │
│   │  • useEffect setup (current)                                        │  │
│   │  • Non-blocking, can be interrupted                                 │  │
│   └─────────────────────────────────────────────────────────────────────┘  │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

For now, rest your eyes on Phases 3, 4, and 5 — those are exactly what Module 2 just taught. Phase 3 is the render half: beginWork going top-down, completeWork going bottom-up, all in memory. Phase 4 is the commit half from lesson 2.2: it touches the real DOM and ends with the atomic swap root.current = finished — the moment the two trees change roles. Phase 5 fires useEffect afterwards, once the browser has painted.

The real code

There is almost no literal code in these maps — they are pictures. But Phase 3 hides one tiny, real loop. This is the heart of the interruptible render: keep doing one unit of work until either the tree is finished (workInProgress becomes null) or the browser asks for time back (shouldYield()).

while (workInProgress !== null && !shouldYield()) {
    performUnitOfWork(workInProgress);
}

Every box in Phase 3 — beginWork, the bailout check, completeWork — happens inside one call to performUnitOfWork. The loop just keeps calling it. That is the entire render phase, in three lines.