Every time you call setState, React somehow figures out what changed, what to update, and what to leave alone. All without you thinking about it. But have you ever wondered how?
The answer is Fiber.
Fiber is React's internal engine. It's the system that decides which components to re-render, in what order, and whether it can pause halfway through to keep your app responsive. Once you understand Fiber, patterns like React.memo, Suspense, and transitions stop feeling like magic and start making sense.
This is a two-part series. In Part 1, we'll build the foundation: what a Fiber is, how the tree is structured, and how React walks through it. In part 2, we'll go deeper into reconciliation, double buffering, hooks, the commit phase, and why interruptible rendering changes everything.
Let's open up the engine and look inside.
What You'll Walk Away With
By the end of Part 1, you'll understand that a Fiber is just a JavaScript object. One node in a tree React builds behind the scenes. You'll see how these nodes link together with child, sibling, and return pointers, forming a structure React can walk through efficiently. And you'll learn about the two-phase traversal (down with beginWork, back up with completeWork) and the work loop that drives it all.
Why should you care? Fiber is the backbone of concurrent React. Understanding it turns React from a black box into a system you can predict and debug. Suddenly, you'll know why
React.memoprevents re-renders, how Suspense knows to show a fallback, and what time-slicing actually means under the hood.
So What Exactly Is a Fiber?
Strip away the jargon, and a Fiber is a plain JavaScript object. That's it. Each one holds information about a single piece of your UI: a component, a DOM element, a text node. Think of it as React's internal sticky note for that piece. What type it is, what props it received, what state it holds, and what work needs to be done.
Here's the key difference from the element tree you're used to: React elements get recreated every render. Fiber nodes don't. They persist. React mutates them in place, updating their props and state rather than throwing them away and rebuilding from scratch. This is what allows React to track things across renders. Hooks, effects, scheduling priorities, all of it lives on the Fiber.
Here's a simplified look at what a Fiber node holds:
interface Fiber {
// What am I?
tag: number // FunctionComponent, HostComponent, etc.
type: string | Function // 'div', 'span', or YourComponent
// Where do I sit in the tree?
child: Fiber | null // My first child
sibling: Fiber | null // My next sibling
return: Fiber | null // My parent
// What are my inputs and outputs?
pendingProps: object // New props coming in this render
memoizedProps: object // Props from the last render
memoizedState: unknown // Current state (hooks linked list lives here)
// What work needs doing?
flags: number // Placement, Update, Deletion (bitmask)
subtreeFlags: number // Aggregated flags from all my children
// How urgent is this work?
lanes: number // Priority lanes for this fiber
childLanes: number // Priority from children
// Double buffering
alternate: Fiber | null // My twin in the other tree
}
Don't try to memorize every field. The important takeaway is that each Fiber knows three things: what it is, where it sits in the tree, and what work needs to happen to it.
How Fibers Connect: The Linked List Tree
If you've worked with tree structures before, you might expect each node to have a children array. Fiber doesn't do that. Instead, it uses three pointers:
-
childpoints to the first child only -
siblingpoints to the next sibling -
returnpoints back to the parent
This forms a linked list you can walk with simple pointer-following. No arrays, no iteration, no index tracking.
Let's make this concrete. Say you have this component tree:
<App>
<Header />
<Main>
<ItemA />
<ItemB />
</Main>
</App>
React builds this Fiber structure:
App
│
├─child──→ Header ──sibling──→ Main
│ │ │
│ return → App child──→ ItemA ──sibling──→ ItemB
│ │ │
│ return → Main return → Main
│
Notice how App only knows about Header (its first child). To find Main, React has to follow Header.sibling. To get back to App from anywhere below, it follows return pointers up.
This structure is what makes React's traversal so predictable, and so interruptible.
The Two-Phase Walk: beginWork and completeWork
React doesn't render your whole tree in one shot. It walks the Fiber tree in a depth-first order, processing each node in two phases:
Phase 1, beginWork (going down): React visits a Fiber, runs the component (calls your function component or class render method), and figures out what its children should look like. Then it moves to the first child and repeats.
Phase 2, completeWork (bubbling up): When a Fiber has no more children to process, React "completes" it. This is where it prepares any DOM updates. Then it moves to the sibling. If there's no sibling, it goes up to the parent via return and completes that too.
Here's the walk for our example tree:
1. beginWork(App) → go to child
2. beginWork(Header) → no child, so complete it
3. completeWork(Header) → go to sibling
4. beginWork(Main) → go to child
5. beginWork(ItemA) → no child, complete it
6. completeWork(ItemA) → go to sibling
7. beginWork(ItemB) → no child, complete it
8. completeWork(ItemB) → no sibling, go up (return)
9. completeWork(Main) → no sibling, go up (return)
10. completeWork(App) → done!
This is the heartbeat of every React render. Every component you've ever written gets processed through this exact loop.
The Work Loop
So what actually drives this walk? A function called workLoopSync (or workLoopConcurrent for interruptible renders). It's surprisingly simple, just a while loop:
function workLoopSync() {
while (workInProgress !== null) {
performUnitOfWork(workInProgress)
}
}
function workLoopConcurrent() {
while (workInProgress !== null && !shouldYield()) {
performUnitOfWork(workInProgress)
}
}
That's the entire difference between sync and concurrent mode: one extra check, shouldYield(). This function asks the scheduler "have we been working too long? Does the browser need the thread back?" If yes, React stops the loop and gives control back. The workInProgress pointer stays right where it is, so React can pick up exactly where it left off on the next frame.
performUnitOfWork is where the two phases come together:
function performUnitOfWork(fiber) {
// Phase 1: process this fiber and get its child
const next = beginWork(fiber)
if (next !== null) {
// Has a child, move down
workInProgress = next
} else {
// No child, complete this fiber then move to
// sibling or bubble up via return
completeUnitOfWork(fiber)
}
}
That's it. beginWork returns the next child (or null). If there's a child, we go deeper. If not, we complete and look for a sibling or parent. The whole tree traversal is just this loop running over and over, one Fiber at a time.
Reconciliation: How React Diffs the Trees
The render phase isn't just walking the tree. It's also comparing the new output with the old one. This process is called reconciliation, and it happens inside beginWork.
When React calls your function component and gets back a new set of children, it doesn't throw away the old Fibers. Instead, it compares the new elements with the existing Fibers to figure out what actually changed. The rules are straightforward:
Same type and key? React keeps the Fiber, updates its props, and moves on. This is why React.memo works. If React sees the same component type and the props haven't changed, it can bail out of beginWork entirely and skip the whole subtree.
Different type? React marks the old Fiber for deletion and creates a new one. This is why swapping <div> for <section> unmounts and remounts everything inside it.
Lists with keys? React uses the key prop to match old Fibers with new elements, even if their order changed. Without keys, React matches by index, which leads to subtle bugs when items get reordered.
Old Fibers: New Elements: What React does:
───────────── ────────────── ─────────────────
<Header /> → <Header /> Same type → reuse, update props
<Main /> → <Sidebar /> Different type → delete Main, create Sidebar
<Footer /> → <Footer /> Same type → reuse, update props
<Banner /> New element → create with Placement flag
The output of reconciliation is an updated Fiber tree where each node is tagged with the right effect flags. Nothing touches the DOM yet.
Test Your Knowledge (Part 1)
Q1: What is a Fiber in React?
- [ ] A DOM node wrapper
- [ ] A JavaScript object representing a unit of work in React's internal tree
- [ ] A Web Worker thread for parallel rendering
- [ ] A CSS-in-JS engine
Q2: How are Fiber nodes connected in the tree?
- [ ] Each Fiber has a children array
- [ ] Fibers use child, sibling, and return pointers (linked list)
- [ ] Fibers are stored in a flat array sorted by depth
- [ ] Fibers reference each other by string IDs
Q3: In what order does React traverse the Fiber tree during rendering?
- [ ] Breadth-first (level by level)
- [ ] Random order based on priority
- [ ] Depth-first: beginWork goes down, completeWork bubbles up
- [ ] Bottom-up only
Reveal all answers
Q1: A JavaScript object representing a unit of work in React's internal tree. A Fiber is a plain JS object that holds information about a component, its props, state, position in the tree, and what side effects need to be performed. Each React element gets a corresponding Fiber node.
Q2: Fibers use child, sibling, and return pointers (linked list). This linked list structure means traversal is just pointer-following. No array allocation, no index management. child points to the first child, sibling to the next sibling, and return back to the parent.
Q3: Depth-first: beginWork goes down, completeWork bubbles up. React processes a Fiber in beginWork and moves to its child. When there's no child, completeWork runs and React moves to the sibling or bubbles up via return.
What's Coming in Part 2
Now that you know what a Fiber is, how the tree is wired, and how React walks through it, we're ready to go deeper. In Part 2, we'll cover:
-
Double buffering and the
current↔workInProgressswap - Effect flags and how React knows exactly what DOM work to do
- Priority lanes and why not all state updates are equal
- Where hooks actually live on the Fiber (and why call order matters)
- The commit phase broken into its three sub-phases
- Interruptible rendering and how it all ties together
See you there.
Skills: React · Fiber Architecture · Reconciliation · Concurrent Rendering · Virtual DOM



Top comments (0)