Back
Frontend Internals

Things React Hides From Developers

Exploring the reconciliation engine, fiber architecture, and why your component re-renders seven times.

2026-06-0815 minadvanced
#react#rendering#performance#internals
EST CONFIGNo response
667 words · 161 lines
Xonoxc

Problem

React is famously "magical." You update state, the UI updates. But what actually happens between setState and the DOM mutation is a black box for most developers.

Initial Assumptions

[!] Pitfall

I thought React diffs virtual DOM trees recursively. This is what the docs implied with "the diffing algorithm."

The reality is more sophisticated. React uses a Fiber architecture — a linked list traversal that can be paused, resumed, and prioritized.

setState → enqueueUpdate → scheduleUpdate → workLoop
                                                ↓
                                         performUnitOfWork
                                                ↓
                               beginWork → completeWork → next unit
                                                ↓
                                         commitRoot → mutate DOM

React's scheduler runs the work loop when the main thread is idle. Each fiber node is a unit of work that can be interrupted.

Implementation

The Fiber Node

Each React element becomes a Fiber:

interface Fiber {
  tag: WorkTag           // FunctionComponent, ClassComponent, HostComponent, etc.
  type: any              // The actual component function/class
  key: string | null
  stateNode: any         // Instance or DOM node
  child: Fiber | null
  sibling: Fiber | null
  return: Fiber | null   // Parent fiber
  pendingProps: any
  memoizedProps: any
  memoizedState: any    // Hook state stored here
  effectTag: number     // Placement, Update, Deletion, etc.
  nextEffect: Fiber | null
  alternate: Fiber | null // The "work-in-progress" tree
}

Reconciliation as a Linked List Walk

React doesn't recurse. It walks a linked list:

> Key Insight

The fiber tree is a singly-linked list where each node has:

  • child — first child
  • sibling — next sibling
  • return — parent

Reconciliation is a depth-first traversal using a while loop, not recursion. This is what makes it interruptible.

function workLoop(deadline: IdleDeadline) {
  while (nextUnitOfWork && deadline.timeRemaining() > 0) {
    nextUnitOfWork = performUnitOfWork(nextUnitOfWork)
  }

  if (nextUnitOfWork) {
    requestIdleCallback(workLoop)
    return
  }

  commitRoot()
}

Problems Encountered

Unnecessary Re-renders

Understanding the fiber tree explains why context consumers re-render even when the consumed value hasn't changed:

[!] Pitfall

The issue is that React walks the fiber tree depth-first. When a context provider's fiber is marked as "needs update," all children are visited. The bailout happens at the child fiber level, but the traversal still happens.

function beginWork(fiber: Fiber): Fiber | null {
  if (canBailout(fiber)) 
     return cloneChild(fiber)
  // ... process updates
  return fiber.child
}

"Bailout" means React still visits the fiber to check if it can bailout. This is O(n) even for components that don't change.

> Key Insight

Solutions ranked by effectiveness:

  1. Move state down — keep changing state close to where it's consumed
  2. useMemo / useCallback — stable references prevent child work
  3. React.memo — shallow comparison bailout at the fiber level
  4. Context splitting — don't put everything in one context

Each prevents work at a different level of the fiber walk.

Tradeoffs

[ Tradedoffs.md ]
StrategyFiber visits savedMental overheadBundle impact
Move state downMostLowNone
useMemoSomeMediumTiny
React.memoComponent subtreeLowTiny
Context splittingConsumer subtreeMediumLow
useCallbackChild renderMediumTiny
useMemo on context valueAll consumersLowNone

Final Mental Model

> Key Insight

The key insight: React's performance characteristics are determined by the fiber tree structure, not the component hierarchy.

Two components that look adjacent in JSX may be far apart in the fiber tree (because of fragments, forwardRef, memo wrappers). Conversely, deeply nested components that bailout are essentially free.

The mental model that helped me:

"Every fiber is a unit of interruptible work. The fiber tree is the execution plan, not the state snapshot. React builds a new plan every render cycle and executes it when the browser is idle."

Improvements

  • React Compiler (automatic memoization)
  • Better context selectors (like Zustand)
  • Offscreen API for hidden trees
  • Server components eliminate the fiber walk entirely for static subtrees