TL;DR / Fiber is React's internal data structure — a linked-list tree of work units — that enables incremental rendering, priority-based scheduling, and the ability to pause and resume render work.
How It Works
Fiber Tree (linked list):
┌────────────┐
│ App │ return
│ fiber │←───────────────────────────
│ │
└────────────┘
│
│
child │
┌─────────────┘
│
│
↓
┌────────────┐ ┌────────────┐ ┌────────────┐
│ Header │ sibling│ Main │ sibling│ Footer │
│ fiber │───────→│ fiber │───────→│ fiber │
│ │ │ │ │ │
└────────────┘ └────────────┘ └────────────┘
Each fiber stores: type, props, state,
effectTag, lanes (priority), alternate
(double-buffering for work-in-progress).
React Fiber is the reimplementation of React's core algorithm, introduced in React 16. The name "Fiber" refers both to the architecture and to the individual data structures — fiber nodes — that represent units of work in the component tree. Before Fiber, React used a recursive stack-based reconciler that could not be interrupted. Fiber replaced this with an iterative, linked-list-based reconciler that can pause, resume, and abort work.
The Fiber Node
A fiber is a plain JavaScript object that represents a component instance (or a DOM element, or a fragment). Each fiber holds:
type— the component function/class or DOM tag string ('div','span').key— the reconciliation key for this element.stateNode— the component instance (class components), DOM node (host elements), ornull(function components).pendingProps/memoizedProps— the incoming and previously committed props.memoizedState— the linked list of hooks (function components) or component state (class components).effectTag(nowflags) — a bitmask indicating what work needs to be done: placement, update, deletion, etc.lanes— the priority lanes that this fiber has pending work for.alternate— a pointer to the fiber's counterpart in the other tree (see double buffering below).
The Linked-List Tree
Instead of a traditional tree with an array of children per node, fibers use three pointers:
child— points to the first child fiber.sibling— points to the next sibling fiber.return— points to the parent fiber.
This structure converts the tree into a singly linked list that can be traversed without recursion. The work loop follows a depth-first traversal: go to child, process it, then go to sibling, process it, and when there are no more siblings, go to return (back up to the parent) and continue to the parent's sibling. This iterative traversal is what makes interruption possible — at any point, React can save a pointer to the current fiber and resume later.
Double Buffering: Current and Work-In-Progress
React maintains two fiber trees at all times. The current tree represents what is currently committed to the DOM. The work-in-progress (WIP) tree is being constructed during a render.
When a render starts, React clones the current tree into the WIP tree (reusing fibers where possible via the alternate pointer). It processes updates against the WIP tree, computing new state, running render functions, and marking effects. When the render completes, React swaps the trees: the WIP tree becomes the current tree, and the old current tree becomes the WIP tree for the next render.
This double-buffering strategy means React never mutates the committed tree during render. The current tree always represents a consistent, committed state. If a render is interrupted or discarded (as in concurrent rendering), the current tree is untouched — there is nothing to roll back.
The Work Loop
The fiber reconciler's core is the workLoopConcurrent function (simplified):
while (workInProgress !== null && !shouldYield()) {
performUnitOfWork(workInProgress);
}
performUnitOfWork processes one fiber: calls the component's render function, diffs children, and advances workInProgress to the next fiber. shouldYield() checks the time deadline (approximately 5ms per slice). When the deadline passes, the loop exits, and React schedules a continuation via MessageChannel.
For synchronous (urgent) updates, the work loop uses workLoopSync, which omits the shouldYield() check and processes the entire tree without interruption.
Effects and the Commit Phase
During the render traversal, fibers accumulate effect flags: Placement (new node to insert), Update (props or state changed), Deletion (node to remove), Passive (has a useEffect), Layout (has a useLayoutEffect). These flags form an effect list — a linked list of fibers that need DOM work.
The commit phase walks this effect list and applies DOM mutations. It runs in three sub-phases: beforeMutation (reads DOM for getSnapshotBeforeUpdate), mutation (applies insertions, updates, deletions), and layout (runs useLayoutEffect callbacks and ref attachments). After commit, useEffect callbacks are scheduled asynchronously.
Why Fiber Matters
Fiber architecture is the foundation for every modern React feature. Concurrent rendering requires interruptible work — fibers provide it. Suspense requires the ability to pause a subtree and render a fallback — fibers make subtrees independently addressable. Transitions require priority lanes — fibers carry lane information. Server Components require serializable tree descriptions — the fiber structure maps directly to the RSC payload format.
Gotchas
- Fibers are an internal implementation detail. No public API exposes fiber nodes directly. Accessing them via
_reactFiberproperties on DOM nodes is unsupported and may break across versions. - Double buffering means memory usage is roughly 2x the tree size during renders. For extremely large component trees (10,000+ nodes), this can be significant.
- Fiber reuse heuristics can cause subtle bugs. React reuses fiber nodes when keys and types match, preserving state. If you unintentionally reuse a key, a component may retain stale state from a different data item.
- The work loop processes one fiber at a time. If a single component is very expensive (e.g., renders 5,000 child elements synchronously), that single fiber cannot be interrupted. Break large lists into smaller batched components.
- Fiber traversal order affects hook ordering. Hooks are stored as a linked list on the fiber. Calling hooks conditionally (violating the rules of hooks) corrupts this list because the fiber expects a fixed hook order on every render.