Skip to main content

React Fiber Is a Rewrite of the Reconciler Enabling Incremental and Interruptible Rendering

On April 18, 2017, Facebook announced React Fiber, a complete rewrite of the algorithm that decides what to change on screen. React 16.0 shipped it to everyone on September 26, 2017. The public API barely moved. The component syntax did not change at all. What changed was the engine underneath, and that engine is the reason modern React can pause work, throw it away, and resume it a frame later.

The old engine was informally called the stack reconciler. It walked the component tree recursively, comparing the previous element tree with the next one and collecting the updates it needed to apply. Recursion is elegant until it meets a browser. Every recursive call occupies a slot on the JavaScript call stack, and the call stack cannot be saved and restored halfway through. Once a render started, it ran to completion, no matter how long that took or what the user was typing at the time. On a large tree with expensive components, that meant dropped frames and input that felt like it was stuck in treacle.

Fiber takes the oldest trick from operating systems and applies it to user interfaces: if you cannot interrupt a call stack, stop using the call stack for scheduling. A fiber is a plain JavaScript object that represents a unit of work, with fields for the component type, the pending props, the state, and pointers to its first child, its next sibling, and its parent. The tree of fibers is a linked list traversed by a loop instead of by recursion. Because every position in that traversal is data in the heap, the scheduler can walk a few nodes, check whether it has run out of its time budget, and hand control back to the browser. Nothing is lost when it stops, and the next slice simply continues from the fiber it was holding.

That loop is split into two phases, and the distinction explains a lot of React behavior that otherwise looks arbitrary. The render phase is the reconciliation pass: it builds the work in progress tree, calls component functions, and can be interrupted, restarted, or discarded. The commit phase applies the collected mutations to the real DOM and runs layout effects, and it is synchronous and cannot be interrupted. This is why render functions must be pure. If a render is thrown away halfway through, anything you did as a side effect during it stays done, which is exactly how people end up with duplicated network requests and counters that increment twice in development.

Fiber also introduced a priority model. Not all updates are equal: a keystroke that updates a text field matters more than a background list refresh that no one is looking at yet. The scheduler assigns expiration times, sorts work accordingly, and newer versions refined this into lanes, a bitmask system that lets React batch and entangle groups of updates. The immediate payoff in React 16 was error boundaries, fragments, portals, and the ability to return arrays and strings from render. The long term payoff was everything that came later: Suspense, useTransition, useDeferredValue, and the concurrent rendering model that became the default in React 18 in 2022. None of those features would be possible if a render could not be abandoned mid flight.

The rewrite had a cost that teams felt immediately. Several legacy lifecycles, including componentWillMount, componentWillReceiveProps, and componentWillUpdate, were unsafe under a model where a render might run more than once, so they were deprecated in favor of getDerivedStateFromProps and getSnapshotBeforeUpdate. StrictMode started double invoking render functions and effects in development to expose exactly the impurity that Fiber punishes. Code that mutated props or state during render, or that relied on a specific number of renders, broke. In exchange, code that treated rendering as a description of the UI rather than a sequence of instructions became more predictable than it had ever been.

There is a practical lesson here that goes beyond React. The stack reconciler was not badly written, it was built on an assumption that turned out to be wrong: that rendering is fast enough to finish in one go. Fiber replaced that assumption with an explicit scheduler and an explicit representation of pending work. If you have ever fought a janky animation loop or a table that freezes while filtering, you have met the same problem in miniature. The fix is almost always to make the work resumable, not to make it faster.

We use cookies for ads and analytics.