Back to React
React
medium
mid

How does React's rendering process work under the hood?

Two phases: **render** (call components, build a fiber tree, diff against previous) and **commit** (apply DOM changes). Render is interruptible in concurrent mode; commit is synchronous. Each component instance is a Fiber node with state, hooks, and references. Reconciliation pairs elements by type + key; same type → update, different → unmount + mount.

5 min read·~10 min to think through

The two phases

ts
Render phase (interruptible)        Commit phase (sync)
─────────────────────────           ──────────────
Call components                      Apply DOM mutations
Build/update fiber tree              Run layoutEffect
Diff against previous tree           Run effects (async after paint)

Render can be paused, abandoned, or restarted. Commit must complete in one go.

Fiber

Every component instance has a corresponding Fiber node — a JS object holding:

  • Type, props, key.
  • State (useState, hooks linked list).
  • Refs.
  • Pointers to parent/sibling/child.
  • Flags (needs update, etc.).

The fiber tree mirrors the component tree.

Reconciliation

When state changes, React schedules a re-render of the affected subtree. It:

  1. Calls the component function → returns new JSX.
  2. Diffs new element tree against existing fiber tree:
  • Same type + same key: update in place (props change).
  • Different type: unmount old subtree, mount new.
  • Same type, different key: unmount + mount.
  1. Marks fibers with flags for what changed (placement, deletion, update).
  2. Commit applies those mutations to the DOM.

Keys

Keys help React match items across renders in arrays:

jsx
{items.map((i) => <Row key={i.id} item={i} />)}

Stable keys (id) → reordering is cheap. Index keys → re-render rate climbs when order changes; state inside rows tied to wrong items.

Hooks linked list

Each fiber stores hooks as a linked list. Calling hooks in the same order every render is critical — that's why you can't call hooks conditionally:

tsx
if (cond) useState(...)   // BAD — list pointer drifts between renders

Effects vs layoutEffect

  • useLayoutEffect — runs synchronously after DOM mutation, before paint. Use for measurements / synchronous DOM tweaks.
  • useEffect — runs after paint asynchronously. Default; less likely to block.

Concurrent rendering

React 18's scheduler can:

  • Pause a render to let urgent updates jump in.
  • Abandon a render if state changed during it.
  • Hydrate selectively on click priority.

startTransition marks updates as interruptible.

Commit cleanup ordering

On unmount: useEffect cleanups run, then useLayoutEffect cleanups, then DOM removal. On update of an effect: previous cleanup, then DOM, then new effect.

Strict Mode in dev

Mounts → unmounts → mounts again, to expose effect cleanup bugs. Production unchanged.

Performance implications

  • Render is cheap for small trees. Don't memoize prophylactically.
  • Reconciliation cost scales with the size of the subtree React traverses.
  • DOM mutation cost is what commit pays — minimize by keying correctly and avoiding huge subtree swaps.

Interview framing

"Two phases: render (call components, build/diff fiber tree, interruptible in concurrent mode) and commit (apply DOM mutations + run layoutEffect + schedule effects, synchronous). Each instance is a Fiber node holding state, hooks linked list, and tree pointers. Reconciliation matches elements by type + key — same type updates in place; different type or different key remounts. Keys must be stable. Hooks must be called in the same order every render — that's why no conditional hook calls. Concurrent mode (React 18) makes render interruptible — startTransition marks non-urgent updates that can be paused if urgent input arrives."

Follow-up questions

  • Why can't hooks be conditional?
  • useEffect vs useLayoutEffect order?
  • Why are stable keys important?

Common mistakes

  • Index keys for reorderable lists.
  • Conditional hooks.
  • Mutating state in render.
  • Layout work in useEffect instead of useLayoutEffect.

Performance considerations

  • Commit cost grows with subtree changes. Keys + memo on hot subtrees keep work scoped.

Edge cases

  • Strict Mode double-mount surfaces effect cleanup bugs.
  • Render bails when state setter receives equal value (Object.is).
  • Suspense suspending mid-render.

Real-world examples

  • React docs internals, Lin Clark's React Conf fiber talks, OSS React DevTools.

Senior engineer discussion

Seniors understand render vs commit, fiber + keys, and the rules of hooks at a mechanism level.

Related questions