How React Fiber Schedules Work
A visual walk through cooperative scheduling — why React can pause, resume, and abandon rendering work to keep the UI responsive.
Before Fiber, React rendered a component tree in a single, synchronous, uninterruptible recursion. If a render took 50ms, the main thread was blocked for 50ms — no scrolling, no typing, no clicks. Fiber rewrote this so rendering becomes interruptible work the runtime can schedule against the frame budget.
The core idea: work in units, yield to the browser
A browser frame at 60fps is ~16ms. React Fiber breaks rendering into small units of work (one fiber node at a time) and, after each unit, checks: do I still have time in this frame? If not, it yields control back to the browser, lets it paint and handle input, and resumes where it left off on the next idle slot.
Drag the slider below: as the cost per unit rises, fewer units fit before React must yield.
In a 16 ms frame, the scheduler fits 5 work units before it must yield to the browser.
That cooperative yield is the whole trick. Nothing here makes individual work faster — it makes the work preemptible, so a long render no longer monopolizes the thread.
Why a linked-list tree instead of recursion
Recursion stores its progress on the call stack, which you cannot pause and resume.
Fiber instead models the tree as a mutable linked list of fiber nodes (child, sibling,
return pointers). Walking that structure is a loop, not a recursion — and a loop’s position
is just a pointer React can stash and pick back up.
// Conceptually, the work loop:
function workLoop(deadline) {
while (nextUnit && deadline.timeRemaining() > 0) {
nextUnit = performUnitOfWork(nextUnit); // returns the next fiber
}
if (nextUnit) requestIdleCallback(workLoop); // not done — resume later
else commitRoot(); // done — apply to the DOM in one shot
}
Two phases: render (interruptible) and commit (not)
- Render phase — builds the work-in-progress tree, can be paused, resumed, or thrown away entirely if a higher-priority update arrives. Because it might be discarded, it must be side-effect-free.
- Commit phase — applies the finished tree to the real DOM. This is synchronous and uninterruptible: a half-applied DOM would be a visible glitch.
Priorities: not all updates are equal
Fiber tags updates with a priority (a lane). A keystroke is urgent; fetching analytics is
not. The scheduler can interrupt low-priority render work to service an urgent update, then
return to the lower-priority work afterward. This is what makes useTransition and concurrent
features possible.
The mental model that sticks: Fiber turns rendering from a phone call you can’t hang up into a to-do list you can reorder.
What to take away
- Rendering is split into resumable units of work measured against the frame budget.
- The fiber linked-list replaces the call stack so progress is a pointer, not a stack frame.
- Render is interruptible and discardable; commit is atomic.
- Lanes let urgent updates jump the queue.