1.2 How Fibers link: child, sibling, return
React walks the tree of your components constantly — to render it, to pause and resume it, to find a node's parent on the way back up. So the question this lesson answers is a storage question: how does one Fiber know its children and its parent? The surprising answer is that React does not keep an array of children. It uses exactly three pointers per Fiber, and those three pointers are enough to encode the whole tree and to walk it.
A tree, but no arrays of children
If you were designing this from scratch, you'd probably give each node a children array: parent.children = [a, b, c]. React deliberately does not do that.
Instead each Fiber uses a linked structure with exactly three pointers:
.child→ the first child only.sibling→ the next sibling (a linked list, not nesting).return→ the parent (called "return" because that's where the walk returns to after finishing a subtree)
That's the whole vocabulary. Let's take them one at a time, simplest first.
child: only the first kid
A Fiber's .child points to its first child — and nothing else. If a node has five children, .child still points only to the first one. The other four are not reachable directly from the parent.
This is the part that trips people up: there is no .child2, no .children[3]. From a parent you can only step to its first child.
sibling: the rest, as a chain
So how do you reach the other children? You hop along .sibling. The first child points to the second via .sibling, the second points to the third, and so on. The last child's .sibling is null.
In other words, the children of a node form a singly linked list: enter it through the parent's .child, then walk .sibling until you hit null.
Fragment │ .child (first child only) ▼ Header ──.sibling──▶ Content ──.sibling──▶ Footer ──▶ null Header.return = Content.return = Footer.return = Fragment
Notice what the diagram already shows: Header, Content, and Footer are siblings of one another, strung together left to right. None of them is nested inside another.
return: the way back up
The third pointer, .return, points to the parent. Every child of Fragment in the diagram above has .return = Fragment — the first child and every sibling after it.
Why is it called return and not parent? Because of how React walks the tree. When React finishes a node and all of its children, it needs to go back up to keep working — and .return is literally the address it returns to. The name describes the traversal, not just the family relationship. (It does mean the parent, though — the two are the same node here.)
Why three pointers are enough: the walk
These three pointers aren't just storage — together they define a walk. At any Fiber, React asks three questions in order:
- Is there a
.child? Go down into it. - No child — is there a
.sibling? Step across to it. - No child and no sibling — follow
.returnup to the parent, then try its sibling.
That is a depth-first walk: go as deep as you can via child, fan out across sibling, and climb back up via return when a branch runs out. For the example tree below, the visiting order is:
depth-first, child first:
HostRoot → App → Fragment → Header → Content → Sidebar → Main → Footer
(down .child, across .sibling,
up .return when both run out)
The exact functions that drive this walk — beginWork on the way down and completeWork on the way up — are Module 2. For now the point is narrower: three pointers per node are all the structure that walk needs.
The whole tree, from one component
Here is a small component. Watch each element turn into a Fiber, and watch the three pointers wire them together.
function App() {
return (
<>
<Header />
<Content>
<Sidebar />
<Main />
</Content>
<Footer />
</>
);
}
And here is that same tree expressed purely in child / sibling / return links. Read it slowly — every arrow is one of the three pointers.
FiberRootNode (NOT a fiber)
│ .containerInfo = <div id="root"> (real DOM)
│ .current
▼
HostRoot fiber (tag=3) ← top fiber, .stateNode → FiberRootNode
│ .child
▼
App fiber (tag=0, FunctionComponent) ← .return → HostRoot
│ .child
▼
Fragment fiber (tag=7) ← .return → App (the <> </>)
│ .child
▼
Header fiber (tag=0) ← .return → Fragment
│ .sibling
▼
Content fiber (tag=0) ← .return → Fragment (NOT child of Header!)
│ .child │ .sibling
▼ ▼
Sidebar fiber (tag=0) Footer fiber (tag=0) ← .return → Fragment
│ .sibling (sibling of Content, NOT its child)
▼
Main fiber (tag=0) ← .return → Content
.sibling = null
Read the right-hand notes carefully. Content is a sibling of Header (both children of the Fragment), not a child of it. And Footer is a sibling of Content, not nested under it. The indentation in your JSX does not become nesting in the Fiber tree — only real parent/child relationships do.
The real code
These three pointers are real fields, declared right at the top of the Fiber object under a comment that literally reads "Tree structure":
// ── Tree structure ──
this.return = null; // parent fiber
this.child = null; // first child
this.sibling = null; // next sibling
this.index = 0; // position among siblings
There is a fourth field here, index — it just records a child's position among its siblings (0, 1, 2…). It is not another way to reach a sibling; you still walk the .sibling chain. index exists so the reconciler can match children by position across renders.
Show the full Fiber object (for context)
function FiberNode(tag, pendingProps, key, mode) {
// ── Identity ──
this.tag = tag; // what KIND of fiber (0=FunctionComponent, 5=host DOM, 13=Suspense…)
this.key = key; // reconciliation key
this.elementType = null; // the element's type (your function/class, or 'div')
this.type = null; // resolved type
this.stateNode = null; // DOM node, or class instance, or FiberRootNode
// ── Tree structure ──
this.return = null; // parent fiber
this.child = null; // first child
this.sibling = null; // next sibling
this.index = 0; // position among siblings
// ── Ref ──
this.ref = null;
this.refCleanup = null;
// ── Props & state ──
this.pendingProps = pendingProps; // incoming props (the input to this render)
this.memoizedProps = null; // props from the LAST render (for comparison)
this.updateQueue = null; // queue of pending updates
this.memoizedState = null; // last state — the HOOK CHAIN for function components
this.dependencies = null; // context subscriptions ← powers useContext
// ── Mode ──
this.mode = mode; // Concurrent / Strict / etc.
// ── Effects ──
this.flags = 0; // side-effect flags on THIS fiber (Placement, Update, …)
this.subtreeFlags = 0; // OR of all descendants' flags (lets React skip clean subtrees)
this.deletions = null; // children scheduled for removal
// ── Lanes (scheduling) ──
this.lanes = 0; // work scheduled on THIS fiber
this.childLanes = 0; // work scheduled somewhere in its subtree
// ── Double buffering ──
this.alternate = null; // link to this fiber's other copy (current ↔ workInProgress)
}
The other fields — memoizedState, flags, lanes, alternate — drive later lessons. Here we only care about the four under Tree structure.