DEV Community

Cover image for The Role of Fiber in React Rendering (Part 1): The Tree, The Walk, The Loop
Mohammed Abdelhady
Mohammed Abdelhady

Posted on

The Role of Fiber in React Rendering (Part 1): The Tree, The Walk, The Loop

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.memo prevents 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
}
Enter fullscreen mode Exit fullscreen mode

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:

  • child points to the first child only
  • sibling points to the next sibling
  • return points 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>
Enter fullscreen mode Exit fullscreen mode

React builds this Fiber structure:

App
 │
 ├─child──→ Header ──sibling──→ Main
 │            │                    │
 │          return → App        child──→ ItemA ──sibling──→ ItemB
 │                                         │                  │
 │                                       return → Main     return → Main
 │
Enter fullscreen mode Exit fullscreen mode

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!
Enter fullscreen mode Exit fullscreen mode

This is the heartbeat of every React render. Every component you've ever written gets processed through this exact loop.

Fiber Architecture

Fiber Tree Traversal

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)
  }
}
Enter fullscreen mode Exit fullscreen mode

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)
  }
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

The output of reconciliation is an updated Fiber tree where each node is tagged with the right effect flags. Nothing touches the DOM yet.

Reconciliation: Diffing Old vs New


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 currentworkInProgress swap
  • 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)