When you call setState or update a ref, React doesn't immediately update the DOM. Instead, it runs a sophisticated algorithm called reconciliation — the process of computing the minimal set of changes needed to bring the DOM in sync with your component tree. Understanding this is essential for writing performant React applications.
React's original reconciler used a recursive tree diffing algorithm. It was synchronous: once started, it couldn't be interrupted. This caused jank on complex UIs — long renders would block the main thread and freeze animations.
React 16 introduced Fiber — a complete rewrite of the reconciler that makes rendering interruptible, resumable, and prioritised.
Old reconciler (Stack) Fiber reconciler
───────────────────── ─────────────────
Recursive Iterative
Synchronous Async-capable
Non-interruptible Interruptible + resumable
No priority Priority lanes
A Fiber is a plain JavaScript object — a unit of work representing a component instance. Every component in your tree has a corresponding Fiber node.
interface FiberNode {
// Identity
type: string | Function; // "div" | MyComponent
key: string | null;
// Tree links
return: FiberNode | null; // Parent fiber
child: FiberNode | null; // First child
sibling: FiberNode | null; // Next sibling
// State
memoizedState: any; // useState/useReducer state
memoizedProps: object; // Current props
// Work
pendingProps: object; // Incoming props
effectTag: number; // Placement | Update | Deletion
alternate: FiberNode | null; // Previous version (double buffering)
}React's reconciliation happens in two phases:
React traverses the component tree, calling your render functions, and builds a work-in-progress tree by diffing it against the current tree. This phase is interruptible — React can pause, abort, or restart it based on priority.
// Simplified work loop
function workLoop(deadline: IdleDeadline) {
while (nextUnitOfWork && !shouldYield(deadline)) {
nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
}
if (nextUnitOfWork) {
// More work remains — yield to browser, resume later
requestIdleCallback(workLoop);
} else {
// Render complete — commit
commitRoot();
}
}
function shouldYield
Once the render phase is complete, React synchronously applies all the DOM mutations. This phase cannot be interrupted — it must run to completion to avoid a partially-updated UI.
Render Phase (interruptible) Commit Phase (sync)
──────────────────────────── ──────────────────────
Build work-in-progress tree → Apply DOM mutations
Diff old vs new props Call useLayoutEffect
Schedule effects Flush passive effects (useEffect)
React 18 introduced concurrent features and priority lanes — a system for categorising work by urgency.
// Conceptual lane priorities (simplified)
const SyncLane = 0b0000001; // Highest — flushSync
const InputContinuousLane = 0b0001000; // User input (typing)
const DefaultLane = 0b0010000; // Normal setState
const TransitionLane = 0b0100000; // startTransition
const IdleLane = 0b1000000; // LowestThis is why startTransition works — it marks an update as low priority, letting React interrupt it if a higher-priority event (like a keystroke) arrives.
import { startTransition, useState } from "react";
function SearchBox() {
const [query, setQuery] = useState("");
const [results, setResults] = useState([]);
function handleChange(e: React.ChangeEvent<HTMLInputElement>) {
const value
React's diffing runs in O(n) time (not O(n³) like naive tree diffing) by applying two heuristics:
<div> with a <span>; it unmounts and remounts.// Without keys — React diffs by position
// Updating item[0] triggers unnecessary re-renders of all items
{items.map((item) => <ListItem item={item} />)}
// With stable keys — React correctly identifies each element
{items.map((item) => <ListItem key={item.id} item={item} />)}React.memo prevents re-renders when props haven't changed — use it on expensive leaf components.useMemo / useCallback preserve referential equality across renders, preventing unnecessary child re-renders.startTransition is the right tool for deferring expensive non-urgent updates.key on any element to force a full remount when its identity changes.Understanding the Fiber architecture explains why all of these patterns exist and gives you the intuition to apply them at the right time.