Advanced0 questionsFull Guide

React Fiber Architecture Interview Questions

How React works under the hood — the linked list of work units, double buffering, cooperative scheduling, and why Concurrent Mode was impossible before Fiber.

The Mental Model

Before Fiber, React used a recursive algorithm called the stack reconciler. When state changed, React walked the component tree depth-first, calling render functions and diffing nodes in a single synchronous call stack. The problem: JavaScript is single-threaded. That recursive traversal couldn't be paused. If you had a deep component tree — common in real apps — React would hold the main thread for hundreds of milliseconds, preventing the browser from handling user input, animations, or any other work. Fiber is a complete rewrite of React's reconciliation engine, introduced in React 16. The core insight was: instead of representing the component tree as a recursive call stack (which you can't interrupt), represent it as a linked list of work units (which you can process one at a time, yielding between units). Each "fiber" is a plain JavaScript object representing one unit of work — one component. It stores the component type, its current props and state, pointers to its parent, first child, and next sibling. This linked list structure means React can pause after processing any fiber node, store where it left off, and resume later. The second key innovation is double buffering. React maintains two fiber trees simultaneously: the "current" tree representing what's on screen, and the "work-in-progress" tree representing the next render. React builds the work-in-progress tree in the background. When the work is complete, it swaps the two trees atomically in the commit phase. Users never see an incomplete UI. The third key innovation is the priority system (Lanes). Not all updates are equal. A button click should feel instant. A background data refresh can wait. Fiber assigns each update a priority lane and ensures high-priority work always interrupts and completes before low-priority work.

The Explanation

The stack reconciler problem

// Conceptually, the OLD React reconciler worked like this:
function reconcile(element, domNode) {
  // Recursive — cannot be paused mid-execution
  updateDOMNode(domNode, element.props)
  const children = element.props.children
  children.forEach((child, i) => {
    reconcile(child, domNode.childNodes[i])  // recursive call
  })
}
// Problem: JavaScript call stack is synchronous and uninterruptible.
// A component tree 1000 nodes deep = 1000 recursive calls
// that hold the main thread for the entire duration.
// During this time: no input events, no animations, nothing.
// Observed symptom: Typing in an input connected to state
// caused the UI to freeze for 200-500ms on large trees.
// This is what Fiber was built to fix.

What a Fiber node actually is

// A Fiber is a plain JavaScript object. Roughly:
const fiber = {
  // Identity
  type: MyComponent,          // function or class or 'div' etc.
  key: null,
  // Input
  pendingProps: { name: 'Alice' },
  memoizedProps: { name: 'Alice' },   // props from last completed render
  memoizedState: { count: 0 },        // hooks state (linked list of hook objects)
  // Tree structure — the 3 pointers people always miss:
  return: parentFiber,        // pointer to parent (NOT called "parent")
  child: firstChildFiber,     // pointer to first child only
  sibling: nextSiblingFiber,  // pointer to next sibling
  // Effect tracking
  flags: Update | Placement,  // bitmask of what DOM work is needed
  subtreeFlags: 0,            // aggregate flags from all descendants
  // Double buffering
  alternate: workInProgressFiber,  // pointer to the other tree's version of this fiber
  // Scheduling
  lanes: SyncLane,            // what priority is pending work at
  childLanes: 0,              // aggregate lanes of all descendants
}
// The two fields most people miss:
// 1. "return" (not "parent") — React calls it "return" because it's where
//    the fiber "returns" after completing its work (like a call stack frame)
// 2. "alternate" — each fiber has exactly one alternate pointing to its
//    counterpart in the other tree (current ↔ work-in-progress)

The linked list traversal — depth-first, child-then-sibling

// For a tree like:
//     App
//    /   \
//  Nav   Main
//         |
//       Article
// React builds it as a linked list:
// App.child → Nav
// Nav.sibling → Main
// Main.child → Article
// Article.return → Main
// Main.return → App
// Nav.return → App
// React walks this with a simple work loop:
function workLoop(deadline) {
  while (nextUnitOfWork !== null) {
    // Process one fiber. Returns next unit of work.
    nextUnitOfWork = performUnitOfWork(nextUnitOfWork)
    // KEY: check if browser needs the thread back
    if (deadline.timeRemaining() < 1) {
      break  // yield! Resume next frame.
    }
  }
  if (nextUnitOfWork !== null) {
    // Schedule ourselves to run again in the next frame
    requestIdleCallback(workLoop)
  } else {
    // All work done — safe to commit
    commitRoot()
  }
}
// React uses a custom scheduler (not actual requestIdleCallback in prod)
// but the concept is identical: process one fiber, check remaining time, yield if needed.
// This is the fundamental mechanism behind interruptible rendering.

Double buffering — current tree vs work-in-progress

// At any point in time, React holds two trees:
//
// CURRENT TREE                  WORK-IN-PROGRESS TREE
// (on screen right now)         (being built for next render)
//
//     App ←——————alternate——————→ App'
//    /   \                       /   \
//  Nav   Main                  Nav'  Main'
//         |                           |
//       Article                     Article'
//
// Each fiber points to its alternate via the .alternate field.
// React builds the work-in-progress tree by cloning and updating fibers.
// The clone is cheap — it reuses most fields, only changing what needs updating.
// When rendering is complete:
function commitRoot() {
  // Swap the trees atomically:
  root.current = workInProgressRoot
  // The old current tree becomes the new work-in-progress
  // (will be reused as the base for the next render — that's the "double" in double buffering)
}
// Why this matters:
// React can abandon an incomplete work-in-progress tree with no visible consequences.
// The current tree (on screen) is never touched until the new tree is fully ready.
// This is what makes Concurrent Mode safe: users never see half-rendered UIs.

The workLoop and yielding to the browser

// React's Scheduler package implements cooperative multitasking:
// It uses MessageChannel (not setTimeout) for near-zero-delay callbacks.
// Simplified internals:
const channel = new MessageChannel()
let scheduledCallback = null
channel.port1.onmessage = function() {
  if (scheduledCallback) {
    const frameDeadline = performance.now() + 5  // 5ms budget per frame
    scheduledCallback({ timeRemaining: () => frameDeadline - performance.now() })
    scheduledCallback = null
  }
}
function scheduleWork(callback) {
  scheduledCallback = callback
  channel.port2.postMessage(null)  // triggers port1.onmessage asynchronously
}
// React processes fibers within the 5ms budget.
// If the budget runs out, it posts another message and yields.
// The browser gets to run between messages:
// handle input events, run animations, paint — then return control to React.
// 5ms per frame at 60fps = browser gets ~11ms, React gets 5ms.
// At 120fps: browser needs ~3ms, React gets ~5ms.
// The scheduler adapts to the display refresh rate.

Lanes — the priority system

// Every update in React is assigned a lane — a bitmask representing priority.
// Lane values (simplified):
const SyncLane           = 0b0000000000000000000000000000001  // synchronous, highest priority
const InputContinuousLane = 0b0000000000000000000000000000100  // continuous input (e.g. drag)
const DefaultLane        = 0b0000000000000000000000000010000  // normal updates
const TransitionLane1    = 0b0000000000000000000001000000000  // startTransition updates
const IdleLane           = 0b0100000000000000000000000000000  // background work
// How lanes enable priority:
function handleUserTyping(e) {
  // React assigns SyncLane — must update synchronously
  setInputValue(e.target.value)
}
function handleTransition() {
  startTransition(() => {
    // React assigns TransitionLane — can be deferred
    setFilteredResults(computeExpensiveFilter(inputValue))
  })
}
// When a SyncLane update arrives while a TransitionLane render is in progress:
// React interrupts the TransitionLane work
// Processes the SyncLane update immediately (input feels instant)
// Then resumes (or restarts) the TransitionLane work
// This is why clicks feel instant during expensive renders in React 18.
// The click's SyncLane interrupts the background Transition work.
// Batching also uses lanes: React collects all updates in the same event handler,
// assigns them the same lane, and renders them together in one pass.

What "interruptible rendering" does and does NOT mean

// What it DOES mean:
// React can pause building the work-in-progress tree mid-traversal,
// yield the main thread back to the browser,
// handle higher-priority work (input events, animations),
// then resume building the work-in-progress tree.
// What it does NOT mean:
// It does NOT mean React partially updates the DOM.
// The commit phase is ALWAYS synchronous and uninterrupted.
// The user never sees a half-rendered tree.
// Correct mental model:
// [render phase — interruptible]  →  [commit phase — always atomic]
//   build work-in-progress tree       apply all mutations at once
// Consequence: component render functions (the body of your component)
// may be called MORE THAN ONCE for a single visible update.
// React might start rendering a component, yield, then restart from scratch.
// This is why render functions must be pure — side effects in them are dangerous.
// ✗ Wrong: side effect in render
function Component() {
  analytics.track('render')  // will fire multiple times per visible update!
  return <div />
}
// ✓ Correct: side effect in useEffect
function Component() {
  useEffect(() => { analytics.track('render') }, [])  // fires once after commit
  return <div />
}

Common Misconceptions

⚠️

Many developers think Fiber is the name of the data structure alone. Fiber refers to both the data structure (a single JS object representing a component instance) and the entire reconciliation architecture — the scheduler, work loop, double buffering system, and lanes. When an interviewer asks "what is Fiber," they're asking about the architecture, not just the object.

⚠️

Many developers think "interruptible rendering" means React updates the DOM incrementally. It does not. Rendering (building the work-in-progress tree) is interruptible, but committing (writing to the DOM) is always a single synchronous, uninterruptible operation. React never shows the user a partially updated DOM. The work-in-progress tree is never exposed until it's complete.

⚠️

Many developers think React actually uses requestIdleCallback for scheduling. React ships its own scheduler package that uses MessageChannel for near-zero-latency scheduling. requestIdleCallback has browser inconsistencies, a 50ms timeout minimum, and doesn't fire during animations. React's scheduler is more predictable and cross-platform (it works in Node for SSR as well).

⚠️

Many developers think the fiber node "parent" field is called "parent." It's called "return" — named after the call stack concept of "returning" to the caller. This is a detail interviewers use to separate candidates who've actually read the source code from those who've only read blog posts about it.

⚠️

Many developers think double buffering means React keeps the entire previous render for comparison. The alternate tree is the base for the next render, not the previous render. After the commit, the old current tree becomes the new work-in-progress base. React doesn't retain render history — it's always: current (on screen) and alternate (next render, being built or already committed).

⚠️

Many developers think component re-renders correspond 1:1 with fiber tree traversals. With interruptible rendering, React may traverse a fiber subtree multiple times before committing. If a higher-priority update arrives mid-render, React discards the incomplete work-in-progress, processes the urgent update, and rebuilds the work-in-progress from scratch. The fiber tree traversal count per commit can be greater than one.

Where You'll See This in Real Code

The classic React 15 demo that motivated Fiber was a sierpinski triangle that updated thousands of nodes simultaneously. On large screens with deep nesting, React 15 would freeze the entire browser for 300–500ms on each update — during which no user interactions registered. React 16 (Fiber) reduced this to imperceptible frame drops because input events were handled between fiber processing units.

Virtualized list libraries like react-window work better under Fiber because scroll handling (SyncLane) interrupts the rendering of off-screen row content (DefaultLane or TransitionLane). In React 15, a virtualized list with complex row content could still jank on fast scrolling because rendering couldn't yield. In Fiber, the scroll position update processes immediately; the content renders in background chunks.

React DevTools' component profiler relies directly on fiber internals. When you record a session and see which components re-rendered and how long they took, DevTools is reading the fiber tree — specifically the actualDuration and selfBaseDuration fields on each fiber node, which React sets during the render phase. These fields exist on fibers in development mode.

Error boundaries rely on Fiber's error handling in the work loop. When performUnitOfWork throws, React walks up the fiber tree (via the .return pointers) looking for the nearest fiber whose component class defines componentDidCatch. If found, React marks that subtree for a fallback render and restarts the work loop from that fiber. Without the fiber linked list, this recovery would be impossible — there'd be no way to inspect the ancestor chain from a thrown error.

React Server Components work because of the clean separation between Fiber's render phase and commit phase. Server components run their render phase entirely on the server, producing a serialized fiber subtree (RSC payload). The client receives this, deserializes it into fiber nodes, and merges them into the existing fiber tree during its own reconciliation — specifically using the alternate/current mechanism to avoid destroying existing client component state.

Interview Cheat Sheet

  • Stack reconciler (pre-Fiber): synchronous, recursive, uninterruptible — blocked main thread
  • Fiber = linked list of work units (one fiber per component instance)
  • Fiber node key fields: type, pendingProps, memoizedState, return (parent!), child, sibling, alternate
  • "return" not "parent" — named after call stack "return address" concept
  • Double buffering: current tree (on screen) + work-in-progress tree (being built)
  • alternate field: each fiber points to its counterpart in the other tree
  • workLoop: process one fiber, check time budget, yield if needed, resume next frame
  • React uses MessageChannel scheduler, NOT requestIdleCallback (browser incompatibilities)
  • Lanes: bitmask priority system — SyncLane (clicks) > TransitionLane (deferred) > IdleLane
  • Render phase is interruptible; Commit phase is always synchronous and atomic
  • React may call component functions multiple times before one commit — render must be pure
  • Error boundaries work by walking .return pointers up the fiber tree on thrown errors
💡

How to Answer in an Interview

  • 1.When asked "what is a fiber," give the two-part answer: data structure and architecture. "A Fiber is both a JavaScript object representing a component instance — storing its type, props, state, and tree pointers — and the name of the entire reconciliation architecture that replaced the old recursive stack reconciler. The architecture uses these fiber nodes as a linked list, allowing React to traverse the component tree incrementally and yield to the browser between nodes."
  • 2.The "why did React rewrite as Fiber" question is really asking "what was wrong with the stack reconciler." The answer is concurrency: "The stack reconciler was a synchronous recursive algorithm. Once React started a reconciliation pass, it couldn't stop until it finished. On large trees this blocked the main thread for hundreds of milliseconds — no input events, no animations. Fiber solves this by replacing the call stack with an explicit linked list, giving React control over when to pause and resume."
  • 3.The "what is double buffering" question is a direct differentiator for senior roles. Answer with confidence: "React maintains two fiber trees: the current tree representing what's on screen, and the work-in-progress tree being built for the next render. Each fiber has an alternate pointer to its counterpart in the other tree. When a render is complete, React swaps the trees atomically during the commit phase. The old current tree becomes the base for the next work-in-progress. This guarantees the user never sees a half-rendered UI, even when rendering is interrupted."
  • 4.Mention the "render phase can restart" implication when discussing purity in interviews. "Because Fiber can interrupt and restart the render phase, component functions can be called multiple times for a single visible update. This is why React enforces that render functions are pure — any side effects in the render phase can run multiple times. Effects should always be in useEffect, which only runs in the commit phase."
  • 5.Mention the lanes system when discussing React 18's concurrent features. "The lanes system is what makes startTransition possible. Every update is assigned a lane — a priority. Transitions get a low-priority lane. If a high-priority SyncLane update arrives (like a click), React interrupts the low-priority rendering, processes the sync update immediately, then resumes or restarts the transition render. The lanes are a bitmask, so React can efficiently check whether any high-priority work is pending using bitwise AND."

Practice Questions

No questions tagged to this topic yet.

Related Topics

react rendering reconciliation interview questions
Advanced·4–8 Qs
Concurrent Rendering (React 18) Interview Questions
Advanced·4–8 Qs
React Rendering & Performance Interview Questions
Advanced·4–8 Qs
🎯

Can you answer these under pressure?

Reading answers is not the same as knowing them. Practice saying them out loud with AI feedback — that's what builds real interview confidence.

Practice Free →Try Output Quiz