Watch how React traverses the fiber tree using depth-first search during reconciliation.
Fiber is React's internal reconciliation engine, introduced in React 16. Each component in your app has a corresponding Fiber node β a JavaScript object that stores the component type, its props, state, associated DOM node, and pointers to parent, child, and sibling fibers. This linked structure allows React to pause, resume, and prioritize work across multiple frames.
When React renders your component tree, it creates a Fiber node for each element. The node stores: type (function/class/string), props, state, effect hooks, and work-in-progress state from the previous render.
Unlike a recursive call stack (which can't be paused), Fibers are connected as a linked list: each node has a child pointer (first child), sibling pointer (next sibling), and return pointer (parent). This lets React traverse the tree iteratively.
React walks the fiber tree depth-first. It calls 'beginWork' going down (parent β child) and 'completeWork' coming back up (child β parent). This two-pass approach lets React build up an effect list bottom-up.
React maintains two fiber trees simultaneously: the 'current' tree (what's on screen) and the 'work-in-progress' tree (what's being built). Each fiber node has an 'alternate' pointer linking its counterpart in the other tree.
When the commit phase completes, React atomically swaps the two trees by pointing the root's 'current' pointer to the work-in-progress tree. The old current tree becomes the new work-in-progress for the next render.
A JS object representing one unit of work for a component. Contains type, key, ref, props, state, hooks list, effect tags, and tree pointers.
The fiber tree being built during reconciliation. Not visible to the user until the commit phase completes and it's swapped with the current tree.
The two functions React calls as it traverses the fiber tree. beginWork processes a node on the way down; completeWork finalizes it on the way back up.
A bitmask flag on a fiber node indicating what DOM work needs to happen: Placement (insert), Update (patch props), Deletion (remove).
Each fiber has an 'alternate' field pointing to its counterpart in the other tree (current β work-in-progress). Enables double buffering.
1// Simplified Fiber node structure (React internals)2{3 tag: FunctionComponent, // type of fiber4 type: MyComponent, // component function/class/string5 key: null,6 ref: null,7 pendingProps: { id: 1 },8 memoizedProps: { id: 1 }, // props from last render9 memoizedState: hooksList, // linked list of hooks1011 // Tree pointers12 return: parentFiber, // parent13 child: firstChildFiber, // first child14 sibling: nextSiblingFiber, // next sibling1516 // Double buffering17 alternate: workInProgressFiber,1819 // Work tracking20 flags: Update | Passive, // effect tags21 lanes: DefaultLane, // priority22}
The Fiber architecture is what makes React's concurrent features possible. Because work is represented as a data structure (not a call stack), React can pause reconciliation at any fiber node, yield control back to the browser for high-priority tasks like user input, and resume where it left off. This is the foundation of Suspense, transitions, and automatic batching.
Your dashboard app freezes for 200ms when a user opens a settings panel. React DevTools Profiler shows a long 'Render' phase but you can't identify which component is slow.
Without understanding Fiber, the profiler's flame graph (which shows fiber-by-fiber render times) looks like random colored bars. You don't know why some components are 're-rendered' when their props haven't changed.
Understanding that each bar in the Profiler IS a fiber node helps you trace the work loop: parent β child β sibling. You can identify which fiber's `beginWork` is slow, check its `memoizedProps` vs `pendingProps`, and apply React.memo or useMemo precisely.
Takeaway: The React DevTools Profiler is literally a fiber tree visualizer. Understanding fiber architecture transforms it from a confusing chart into a powerful debugging tool.
You have a drag-and-drop interface where moving an item should animate at 60fps, but data fetching from a heavy table re-render causes janky movement.
A single synchronous render blocks the main thread for 50ms+, causing the browser to skip animation frames during the data fetch re-render.
React Fiber's priority lanes system lets you wrap the heavy re-render in `startTransition()`, marking it as low-priority. The fiber work loop can now pause the heavy render, yield to the browser for animation frames, and resume later β all because fibers are interruptible units of work.
Takeaway: Fiber architecture is the reason `useTransition` and `useDeferredValue` exist. These APIs wouldn't be possible without interruptible, priority-based rendering.