skip to content

The Work Loop and Interruptibility

React walks the fiber tree in small units of work — beginWork on the way down, completeWork on the way up — and can stop between units to let the browser handle input or paint. This is the concrete answer to 'why did Fiber replace the old stack reconciler?'.

part ofReactoverview, primer and where to startread it →
on this pageshow

explore

questions

5

Before React 16, React's reconciler walked the component tree with recursive function calls that ran to completion. What about that design made pausing mid-render impossible, and what did the Fiber architecture change so React could stop partway and later resume?

level: middleimportance: must knowfreq 58%

answer

  1. who owns the traversal bookkeeping
  2. recursion hides state you cannot save
  3. frames on the stack versus objects on the heap
  4. a loop with a pointer React controls
  5. stop between units, then return and resume

basics

~20 s

Recursion kept the traversal state in the JavaScript call stack, which React cannot save or resume. Fiber moves that state into heap objects React owns and replaces recursion with a loop, so React can return control and continue from a saved pointer.

solid answer

~50 s

The old reconciler expressed the tree walk as recursion, so "where am I in the tree" lived in the JavaScript call stack. That stack belongs to the engine: React cannot snapshot a half-unwound stack, hand the thread back to the browser, and later restore it — and since a call runs to completion, once the walk started it finished. Fiber replaces the call stack with a data structure React controls. Each fiber is a plain object holding what a frame held: which component this is, its props and state, and where to go next. Reconciliation becomes a loop over one fiber at a time with the next one held in a variable React owns. Between units React checks whether it should yield; if so it simply returns, letting the stack unwind, and resumes later from that saved fiber.

go deeper

for a junior

Recall the headline: the old reconciler recursed and had to finish; Fiber turns the walk into a loop over small units so React can stop between them.

for a middle

Explain where traversal state lives in each design — engine-owned call frames versus heap objects React links itself — and why that is what makes resuming possible.

for a senior

Be ready to say what Fiber does not buy: no faster diff, no interruptible commit, and no help when one component's own work is enormous. Interviewers probe for that precision.

for a principal

Own the architectural argument: React traded implicit language machinery for an explicit, owned scheduler, accepting overhead and a purity contract in exchange for control over when the framework gives back the main thread.

## The question behind the question "Why did React rewrite the reconciler as Fiber?" is really "why can't you pause a recursive algorithm in JavaScript?" Everything else follows from that. ## What the stack reconciler did The pre-React-16 reconciler processed an update by recursing: to reconcile a component, it computed that component's output and then called itself for each child, and each of those calls did the same. The traversal was elegant, and its bookkeeping was implicit — the engine's call stack was the record of which node was being processed and which parents were waiting for it to return. That implicitness is the whole problem. The JavaScript call stack is owned by the engine. There is no API to capture the current stack, discard it, and later restore it; there is no way to say "return to the browser now and come back into the middle of this recursion." And because JavaScript runs each task to completion, nothing can interrupt the recursion from outside either. So an update to a large tree was an all-or-nothing block of synchronous work: once React entered, the browser got the thread back only when React was finished. On a big tree that is a dropped frame, and a keystroke typed during it waits. Notice that this is a structural limitation, not a tuning problem. Adding priorities or a scheduler to the old reconciler would not have helped, because there was still no way to stop. ## What Fiber changed Fiber's core idea is to stop using the call stack as the data structure and build an explicit one instead. A fiber is an ordinary JavaScript object on the heap that holds what a stack frame would have held: which component or host element it represents, its pending props and state, its position relative to its parent, child and sibling, and how much work is left to do on it. With that in place, reconciliation stops being recursion and becomes a loop: ```js // Conceptual shape of React's concurrent work loop. while (workInProgress !== null && !shouldYield()) { workInProgress = performUnitOfWork(workInProgress); } ``` `workInProgress` is a variable React owns pointing at the next fiber to process. Each iteration does the work for exactly one fiber and returns the next one. Between iterations React calls `shouldYield()`, which asks the scheduler whether the current slice of the frame is used up. When the answer is yes, React does not freeze — it returns from the loop normally, so the JavaScript stack unwinds completely and the browser is free to run input handlers, layout and paint. React schedules a callback to continue (the scheduler uses a message channel to obtain a fresh task rather than a clamped timer), and when that callback runs, the loop starts again from the same `workInProgress` value. Nothing was lost, because nothing important was ever on the call stack. ## Why this is the enabling change, not the feature Fiber by itself does not make anything faster; a full render still does the same work, and yielding adds a little overhead. What it buys is the *ability to stop*, and everything in modern React that depends on stopping is built on top of it: interrupting a long render when input arrives, abandoning a render that has been superseded, and rendering some updates at lower urgency than others. It is worth being precise in an interview: Fiber made the render phase interruptible. The commit — the part that mutates the DOM — is still one synchronous block, because there the work is not disposable. ## The granularity that remains The unit React can stop between is one fiber, which for a function component means one call of your component function. React cannot stop inside that call, again because of run-to-completion. So the architecture converts "one huge indivisible block" into "many small indivisible blocks" — a huge improvement when cost is spread across a tree, and no improvement at all when a single component does something enormous. ## A common wrong answer Candidates often say the old reconciler was slow and Fiber made diffing faster, or that Fiber introduced the virtual DOM. Neither is right. The diffing heuristics are largely the same; the virtual DOM predates Fiber. What changed is where the traversal state lives, and therefore who controls when React gives the thread back.

  • If Fiber does not make rendering faster, what does it actually buy?
    The ability to stop. Total work for a full render is roughly unchanged and yielding adds a small overhead, but React can now return the thread mid-tree, so urgent input is handled during a long render instead of after it. Perceived responsiveness improves even though throughput does not. Everything built on interruption — interrupting, abandoning and prioritising renders — depends on that one capability.
  • Once React decides to yield, how does it get control back to continue the loop?
    It returns from the work loop so the call stack unwinds, and schedules a continuation through its scheduler, which obtains a fresh task using a message channel rather than a timer — timers are clamped and would add milliseconds per slice. When that callback runs, React re-enters the loop from the fiber pointer it saved, so the walk continues where it stopped.
  • Why doesn't the same reasoning apply to the commit phase, which is also a tree walk?
    Because commit work is not disposable. Render work is pure computation on heap objects, so stopping or throwing it away costs nothing visible. Commit mutates the real DOM, attaches refs and runs layout effects; yielding partway would let the browser paint a mixture of two states and leave React with mutations it cannot cheaply undo. So commit stays synchronous by design.

saying these in an interview costs you the question

  • Says Fiber made the diffing algorithm faster
  • Claims Fiber introduced the virtual DOM
  • Thinks the old reconciler just lacked a scheduler
  • Says React saves and restores the JavaScript call stack
  • Believes Fiber made the commit phase interruptible too

context

open as a page

In React 19, the render phase can stop partway through the component tree to let the browser handle input or paint. Does that mean a user can end up seeing a partially updated screen, and what happens to the work React had already done before it stopped?

level: juniorimportance: should knowfreq 45%

basics

~20 s

No. React builds the new tree in memory during the render phase and touches the DOM only in the commit phase, which runs as one uninterruptible block. Paused work is either resumed or discarded and redone.

open as a page

React's render phase visits each node of its work tree twice — once on the way down, in what its source calls beginWork, and once on the way back up, in completeWork. What does React do at each of those two visits, and why is the upward visit needed at all?

level: middleimportance: should knowfreq 32%

basics

~20 s

On the way down, React runs the component and reconciles the elements it returns into child nodes. On the way up, it finishes each node — for host elements it creates the detached DOM instance and appends its finished children — then moves to the sibling or the parent.

open as a page

React can pause, resume, or entirely abandon a render pass, yet it applies the finished tree to the DOM in a single synchronous commit that never yields. Why must the commit phase be uninterruptible, and what would break if React yielded partway through it?

level: seniorimportance: should knowfreq 34%

basics

~20 s

Render work is pure and in memory, so stopping or discarding it is free. Commit mutates the real DOM, so yielding partway would let the browser paint a mixture of two states and expose a half-updated tree to refs, effects and other scripts.

open as a page

A React 19 app wraps a state update in startTransition, but the page still freezes for roughly 150 ms on every update because one component's render function runs an expensive synchronous computation. Why does marking the update as a transition fail to keep the page responsive here?

level: seniorimportance: should knowfreq 42%

basics

~20 s

React can only yield between units of work, and one component's render call is a single indivisible unit. A transition lets React interrupt the tree walk, not a function already running, so a 150 ms component body still blocks the frame.

open as a page