Explain React Fiber deeply
Fiber is React's reconciler: a linked-list tree of work units that can be paused, resumed, and prioritized. It's what unlocked concurrent rendering, Suspense, and transitions.
Fiber is the codename for React's reconciler, introduced in React 16. To understand it, start with the problem it solved: pre-Fiber (the "stack reconciler" in React 15), reconciliation was a recursive, synchronous depth-first walk of the component tree. Once setState kicked off a render, React owned the main thread until every component had reconciled and the DOM was patched. A 50ms render meant 50ms of unresponsive input — typing in a search box while a heavy list re-rendered felt broken.
Fiber rewrites reconciliation as a traversable, interruptible data structure. Each fiber is a plain JS object representing one node of work — a component, host element, fragment, etc. Crucially, fibers form a linked list, not a recursive call tree:
return→ parent fiberchild→ first childsibling→ next siblingalternate→ the same fiber in the "other" tree (current vs work-in-progress)pendingProps,memoizedProps,memoizedState,updateQueue,flags/effectTag,lanes
Because traversal is driven by pointer-chasing through these fields rather than the C call stack, React can pause mid-traversal (save the current fiber pointer), check whether it should yield to the browser via the scheduler package, and resume later from exactly that pointer.
Two-phase commit:
- Render / reconciliation phase (interruptible, pure). React builds a work-in-progress (WIP) fiber tree by diffing against the current tree. For each fiber it calls the component, derives child elements, reconciles children (
reconcileChildren), and tags any effects (DOM insertions, refs, lifecycle calls) onflags. No side effects happen here. Between fibers, React can callshouldYield(); if the browser has higher-priority work (a click, a frame), React saves state and bails out, picking up later. If a higher-priority update comes in, the in-progress WIP is discarded and restarted at the higher priority — the render phase is allowed to throw away work because it's pure. - Commit phase (synchronous, non-interruptible). Once the WIP is fully built, React walks the effect list in one pass: applies DOM mutations, runs
componentDidMount/componentDidUpdateanduseLayoutEffectsynchronously, then schedulesuseEffectto run asynchronously after paint. There's no yielding here — atomic, or your DOM is inconsistent.
Double buffering. React always keeps two trees: the current tree (what the user sees, attached to real DOM nodes) and the work-in-progress tree (being built). When commit succeeds, the WIP becomes current. This is why alternate pointers exist: each fiber in one tree can find its counterpart in the other in O(1), allowing diff-by-reference.
Lanes and priority. React 18 generalized "priority" to lanes — a 31-bit bitmask. Each update is tagged with one or more lanes (SyncLane, InputContinuousLane, DefaultLane, TransitionLane, IdleLane, etc.). The scheduler picks the highest-priority lane with pending work. A click can pre-empt a transition; transitions don't pre-empt each other. This is what powers startTransition, useDeferredValue, automatic batching, and Suspense data loading.
Suspense via throw-and-catch. When a component reads data that isn't ready, it throws a Promise. React unwinds the WIP tree to the nearest Suspense boundary, marks it as showing fallback, and registers a listener to retry rendering when the promise resolves. The throw mechanism is only legal because the render phase is pure and discardable.
Why this architecture matters:
- Interruptible rendering enables time-slicing, concurrent features, and
startTransition. - Pure render lets React render off-screen (offscreen API), prerender into a hidden tree, and throw away work.
- The alternate-tree design lets refs, memoization, and effects diff against a stable previous version without copying.
- Lanes + heuristic yielding keep INP low even on heavy pages.
This is also why "rules of hooks" exist: hook state lives on the fiber's memoizedState linked list and is indexed by call order — the entire concurrent model depends on hooks being deterministic per render.
Code
Follow-up questions
- •What's the difference between a fiber and a React element?
- •How do lanes encode priority?
- •Why is the render phase pure — what breaks if you mutate during it?
Common mistakes
- •Thinking Fiber means async by default — most renders still happen in one go.
- •Mutating refs/state during render expecting it to commit.
Performance considerations
- •Work units are small but not free — extremely deep trees still cost.
- •Bailout via memoization (React.memo, useMemo) skips fiber subtrees entirely.
Edge cases
- •Errors thrown in render phase unwind to the nearest error boundary.
- •Suspense boundaries during SSR have totally different semantics from CSR.
Real-world examples
- •React Server Components and streaming SSR are both built on Fiber's interruptible model.