Skip to the solution
HardSystem Design8 min read

How does React Fiber work?

137 views

The problem

Question presented to candidate: "React Fiber is described as a rewrite of the reconciler. What actually changed, and why?"

What a strong answer should cover:

  • Fiber is React's internal representation of a unit of work β€” one JavaScript object per element in the tree, plus the reconciler that walks them.
  • The problem it solved: the old reconciler used recursion, so a render could not be interrupted. Once started, it ran to completion and blocked the main thread.
  • Fiber replaces recursion with a linked-list tree β€” each node has child, sibling and return pointers β€” which turns the traversal into a loop over an explicit structure rather than a call stack.
  • Because the position is data rather than stack frames, React can stop between units, yield to the browser, and resume later β€” that is what makes concurrent features possible.
  • Double buffering: React keeps a pair of fibers per position (current and alternate), building the next tree into the spare one, so the committed tree is never partially mutated.
  • Two phases: the render phase is interruptible and produces a list of effects; the commit phase is synchronous and applies them.
  • Each fiber carries memoizedState (the hooks linked list), memoizedProps, flags (what changed) and lanes (priority).
  • Fiber is an implementation detail β€” do not reach into it in application code.

Clarifying questions expected:

  • "Do you want the data structure, or the scheduling consequences?" β€” they are separable answers.

Code / implementation expected: No. Fiber is internal; describing the structure and what it enables is the answer.

  • fiber
  • architecture
  • reconciliation

Try it yourself

Sketch your approach before you read the solution. That is what the interview tests.

Stuck? Get a hint.

A short nudge toward the approach. It will not give the answer away.

Solution

The solution is hidden so you can try first

8 min read Β· 9 sections

Try it in the playground

Open any example in the playground to run and change it.

ReactReading a real fiber off a DOM node and walking its pointers
import { useState, useRef } from "react";

// NOTE: this reaches into a React INTERNAL to make the structure visible.
// Never do this in application code β€” the property name is deliberately
// randomised, the shape changes between versions, and none of it is public.
function fiberOf(node) {
  const key = Object.keys(node).find((k) => k.startsWith("__reactFiber$"));
  return key ? node[key] : null;
}

const nameOf = (f) =>
  !f ? "null" : typeof f.type === "function" ? (f.type.name || "anonymous") : String(f.type);

function Leaf() {
  return <span>leaf</span>;
}

export default function App() {
  const probe = useRef(null);
  const [n, setN] = useState(0);
  const [out, setOut] = useState([]);

  const inspect = () => {
    const fiber = fiberOf(probe.current);
    if (!fiber) return setOut(["no fiber found β€” React internals may have changed"]);

    // Walk the children: child, then follow sibling. There is no array.
    const kids = [];
    for (let c = fiber.child; c; c = c.sibling) kids.push(nameOf(c));

    setOut([
      "fiber.tag:    " + fiber.tag + "   (5 = host component, a real DOM element)",
      "fiber.type:   " + JSON.stringify(fiber.type),
      "",
      "children, reached via child -> sibling:",
      "  " + kids.join(" -> "),
      "parent, reached via return:",
      "  " + nameOf(fiber.return),
      "",
      "alternate:                    " + (fiber.alternate ? "a paired fiber" : "null (none yet)"),
      "alternate.alternate === it?   " + (fiber.alternate ? String(fiber.alternate.alternate === fiber) : "n/a"),
      "",
      "fields present: " +
        ["child", "sibling", "return", "memoizedState", "memoizedProps", "alternate", "flags", "lanes"]
          .filter((f) => f in fiber)
          .join(", "),
    ]);
  };

  return (
    <div style={{ padding: 24, fontFamily: "system-ui", lineHeight: 1.6, maxWidth: 600 }}>
      <div style={{ display: "flex", gap: 8, marginBottom: 12 }}>
        <button onClick={inspect}>read the fiber</button>
        <button onClick={() => setN((v) => v + 1)}>re-render ({n})</button>
      </div>

      {/* The node whose fiber we read. It has two children of different kinds. */}
      <div ref={probe} id="probe" style={{ border: "1px dashed #aaa", borderRadius: 8, padding: 10 }}>
        <Leaf />
        <b> Β· render count {n}</b>
      </div>

      <pre style={{ background: "#f6f6f8", padding: 10, borderRadius: 8, fontSize: 12, marginTop: 12, minHeight: 190 }}>
{out.length ? out.join("\n") : "press 'read the fiber'"}
      </pre>

      <p style={{ fontSize: 13, color: "#666" }}>
        Read it once, then press re-render and read it again: <code>alternate</code>
        starts as null and appears after the first update β€” React allocates the
        second buffer lazily. Note there is no children array anywhere; the
        second child is reached by following <code>sibling</code>.
      </p>
    </div>
  );
}

Discussion

Other approaches, follow-ups and how it went in real interviews.

Sign in to join the discussion.

No comments yet. Share how you would answer it.

Question 117 of 119 in React.js

Keep your momentum going.

Mark this question solved to track your progress, then take the next one while it is fresh.

All React.js questions
Up nextHard

How does automatic batching work in React 18, and how do you opt out?

Continue