Skip to the chapter
←ReactGGroundwork⚛React
React/Advanced/A1
34 / 57 written chapters
/

The cover
  1. B1Setup & JSX
  2. B2Thinking in React
  3. B3Components
  4. B4Props
  5. B5useState
  6. B6Events & conditionals
  7. B7Lists & keys
  8. B8Forms
  9. B9useEffect
  10. B10Lifting state & styling
  11. B11Guided project
  12. ★Cheat page
  1. I1useRef
  2. I2Context
  3. I3useReducer
  4. I4Custom hooks
  5. I5Effects in depth
  6. I6Routing
  7. I7State architecture
  8. I8Memoisation
  9. I9Boundaries & portals
  10. I10Data fetching
  11. I11Forms at scale
  12. I12State libraries
  13. I13Auth
  14. I14TanStack Query
  15. I15Testing
  16. I16TypeScript
  17. I17Animation
  18. I18E2E testing
  19. I19i18n
  20. I20File uploads
  21. I21Real-time UI
  22. I22shadcn/ui
  23. ★Cheat page
  1. A1Fiber
  2. A2Concurrent
  3. A3Suspense
  4. A4Server Components
  5. A5Server Actions
  6. A6use()
  7. A7Rendering strategies
  8. A8Effect timing
  9. A9External stores
  10. A10Refs in depth
  11. A11Component patterns
  12. A12Performance
  13. A13Accessibility
  14. A14Design systems
  15. A15Advanced TypeScript
  16. A16Architecture at scale
  17. A17Custom renderers
  18. A18Migrating & upgrading
  19. A19Production debugging
  20. A20Review & judgement
  21. A21State machines
  22. A22Feature flags
  23. A23Interview bank
  24. ★Cheat page

⚛A1 · Part 3 · Advanced

Reconciliation & Fiber

Render is a pure, interruptible calculation. Commit is a synchronous mutation. Everything follows from that split.

7 min read11 sections2 exercises
34 of 57

In this chapter

  • The two phases
  • What a fiber is
  • Double buffering
  • The diffing rules, exactly
  • Lanes: not everything is equally urgent

and 6 more

about 7 min
11On this page
  1. The two phases
  2. What a fiber is
  3. Double buffering
  4. The diffing rules, exactly
  5. Lanes: not everything is equally urgent
  6. Walking the tree, concretely
  7. Bailouts: how React skips work
  8. Flags, and how commit finds the work
  9. Watch it happen: render, then commit
  10. Where the time actually goes
  11. Reading a Profiler flame chart

The two phases

RenderCommit
What happensYour components run; React builds a tree of workThe DOM is mutated, refs attach, layout effects run
Interruptibleyesno
Can run twiceyes — and does, in Strict Modenever
Side effects allowednonethis is where they belong

This is why components must be pure. React may start rendering, throw the work away because something more urgent arrived, and start again from the top. A component that mutated something outside itself would have done it twice, or half.

What a fiber is

A fiber is a plain object representing one unit of work — one component instance, one DOM element. It holds the type, the pending props, the state hooks, the flags describing work it needs, and pointers to its child, sibling and return (parent).

{ type: Button, stateNode, memoizedProps, memoizedState,
  child, sibling, return, flags, subtreeFlags, alternate }

Those pointers are the important part. Before Fiber, React reconciled with recursion — and a recursive call stack cannot be paused. A linked list can: React keeps a pointer to the next fiber, so it can stop after any node, hand control back to the browser, and resume from that pointer later.

The whole point of the rewrite

Fiber turned rendering from a recursive function you cannot interrupt into a loop over a data structure you can. Everything React shipped afterwards — time slicing, transitions, Suspense, streaming — needed that one capability.

Double buffering

There are two trees. current is what is on screen; workInProgress is what is being built. Each fiber points at its counterpart through alternate, and React reuses those objects rather than allocating a new tree every render.

When the render finishes, React swaps the pointer — the work-in-progress tree becomes current in one assignment. If the render is abandoned instead, the in-progress tree is simply discarded and the screen never showed a partial state. This is why an interrupted render is invisible rather than glitchy.

The diffing rules, exactly

A true tree diff is O(n³). React gets to O(n) by making two assumptions that hold in practice:

  1. Different types produce different trees. If the element type at a position changed, React does not try to match children — it unmounts the whole subtree and mounts a new one. State, refs and DOM nodes below are all discarded.
  2. Keys identify children across renders. Within a list, a key tells React which new element corresponds to which old one, so it can move a node instead of rebuilding it.
<div><Counter /></div>   →   <span><Counter /></span>
// div became span → Counter unmounts and remounts, losing its state

This is the mechanism behind the two bugs from the beginner tier: a component defined inside another component is a new function — a new type — every render, so rule one destroys it each time; and index keys make rule two match the wrong items.

Lanes: not everything is equally urgent

Each update is tagged with a lane, a priority. A click is more urgent than a transition, which is more urgent than an offscreen prerender. React works through the highest-priority lane first, and can abandon lower-priority work when something urgent arrives.

setInput(value);                          // urgent — user is typing
startTransition(() => setResults(list));  // can be interrupted

Concurrent features are the public API over this. Without lanes, every update is equally urgent, and one expensive re-render blocks the keystroke behind it.

Walking the tree, concretely

beginWork(fiber)      // going down: run the component, create child fibers
completeWork(fiber)   // coming back up: finish the node, bubble its flags to the parent

React descends through child pointers calling beginWork, and when a fiber has no child it calls completeWork and follows sibling, or return if there is no sibling. Depth-first, with an explicit pointer instead of the call stack — which is precisely what makes it pausable.

Between units, React checks whether it has run out of its time slice. If it has, it yields to the browser and schedules a continuation. The browser gets to paint, handle input, and run its own work; React picks up from the fiber it stopped at.

Bailouts: how React skips work

Not every fiber in the path gets re-rendered. React bails out when it can, and knowing the conditions explains most "why did this render" questions:

  • Props are referentially equal and there is no pending state — React reuses the existing fiber and does not call your component.
  • The element object is identical — passing children straight through means the same element, so the subtree is skipped even though the parent re-rendered. This is why the children pattern beats memo.
  • State was set to the same value — React may re-render once and then bail before touching the DOM.

A bailout stops the walk at that node, so an entire subtree is skipped in one check. That is why re-rendering is usually cheap and why the fix for a slow tree is often structural rather than a cache.

Flags, and how commit finds the work

During render React records what each fiber needs as flags — placement, update, ref, snapshot, has-layout-effect — and deletions are noted on the parent. As completeWork climbs back up, it bubbles those flags upward: each fiber's subtreeFlags is the combination of everything beneath it. So the root knows, without looking, whether any work exists anywhere in the tree, and each parent knows whether it is worth descending into a child.

Commit then walks the finished tree in three passes — before mutation (snapshots), mutation (DOM changes, refs detached), layout (refs attached, useLayoutEffect) — and in each pass skips any subtree whose subtreeFlags show nothing to do. A change in one leaf costs a walk down one path, not a scan of every component.

Older write-ups, and React 16 and 17, describe an effect list: a linked chain of only the fibers with effects, threaded together during render. React 18 replaced it with flags and subtreeFlags, so if you read "commit walks the effect list", you are reading about an implementation that no longer exists. The three passes and their guarantees are unchanged.

That three-pass structure is why effect timing is what it is: refs are attached in the layout pass, which is why every effect can rely on ref.current, and why nothing can during render.

Watch it happen: render, then commit

Walking the fiber tree
Work order so far

Only ItemB has changed. Step through: the render phase walks down and back up, bubbling that one flag toward the root; then commit walks the finished tree and skips every clean subtree.

Where the time actually goes

A common misreading is that the virtual DOM is fast. It is not — it is overhead that buys a programming model. Direct DOM manipulation is faster than diffing; what React gives you is not having to write it.

In a slow React app the cost is almost never the diff. It is your component functions running: an expensive calculation with no memo, a context provider high in the tree handing out a new object every render, a list rendering two thousand rows that should be virtualised. The Profiler tells you which.

Reading a Profiler flame chart

  • Width is time spent in that component and its children.
  • Grey means it did not re-render — memo or bailout worked.
  • Turn on "record why each component rendered". "Parent rendered" on a component whose props never change points at a missing memo; "context changed" points at an unmemoised provider value.
Interview answer, one sentence

"Fiber replaced recursive reconciliation with a linked-list walk, so render became interruptible while commit stayed synchronous — which is why components must be pure and why concurrent features are possible at all. Diffing is O(n) because of two assumptions: different types mean a new subtree, and keys identify children across renders."

02Practice this chapter

Opens in the editor: write it, run it, and check it against real tests.

  • 8 testsReconcile a list of children by handadvanced
  • 8 testsWould React even call your component?advanced
All 54 for this level →
A1

Finished reading?

0 of 57 chapters read in this series

→Next stop

  1. I22Previous · I22shadcn/ui
  2. A1you are here
  3. Next · A2Concurrent featuresTelling React which updates can wait — so the urgent one never queues behind them.6 min read

01On this page

11 sections · 0%

  1. 01The two phases
  2. 02What a fiber is
  3. 03Double buffering
  4. 04The diffing rules, exactly
  5. 05Lanes: not everything is equally urgent
  6. 06Walking the tree, concretely
  7. 07Bailouts: how React skips work
  8. 08Flags, and how commit finds the work
  9. 09Watch it happen: render, then commit
  10. 10Where the time actually goes
  11. 11Reading a Profiler flame chart

[] chapters · t top