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.
The two phases
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:
- Calls the component function → returns new JSX.
- 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.
- Marks fibers with flags for what changed (placement, deletion, update).
- Commit applies those mutations to the DOM.
Keys
Keys help React match items across renders in arrays:
{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:
if (cond) useState(...) // BAD — list pointer drifts between rendersEffects 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
commitpays — 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.