Loading...
Loading...
Virtual DOM, Fiber, reconciliation, hooks, and React 18 — how React works under the hood.
How React works under the hood: Virtual DOM, Fiber architecture, render vs commit phases, reconciliation, diffing, hooks storage, state updates, and React 18 concurrent features.
Virtual DOM, Fiber architecture, render vs commit phases, reconciliation and diffing, hooks on Fiber nodes, setState batching, and React 18 concurrent features — how React works under the hood from first render to DOM update.
Understanding React internals helps you write efficient code, debug performance issues, and explain architectural trade-offs in SDE-2 interviews.
This module covers Virtual DOM vs Fiber, the two-phase rendering model, reconciliation rules, why keys matter, how hooks are stored on Fiber nodes, and automatic batching in React 18.
Expect deep-dive questions on useTransition, useDeferredValue, why you cannot call hooks conditionally, and how the commit phase applies DOM updates without blocking the main thread unnecessarily.
Walk through Virtual DOM diffing, Fiber's linked-list structure, reconciliation with keys, hooks storage on fiber.memoizedState, and React 18 automatic batching with concrete diagrams and interview drills.
SDE-2 frontend interviews ask how React updates the DOM, why keys matter, and what happens when you call setState. Internals knowledge explains performance patterns like memo, transitions, and why conditional hooks break. It also helps you debug mysterious double renders and understand when React 18 batching applies.
How React works under the hood — for SDE-2 Frontend Engineer interviews.
React Internals is how React converts your JSX into DOM updates. Understanding this layer helps you write efficient code, debug performance issues, and explain why certain patterns work.
Mental model
An in-memory JavaScript representation of the real DOM.
The Virtual DOM is a lightweight copy of your UI stored as plain JavaScript objects — not actual DOM nodes. Creating objects is fast; touching the real DOM is expensive. React uses VDOM to batch and minimize DOM operations.
// JSX <div className="container"> <h1>Hello</h1> </div> // Becomes React.createElement: React.createElement( "div", { className: "container" }, React.createElement("h1", null, "Hello") ) // Returns a plain object: { type: "div", props: { className: "container", children: { type: "h1", props: { children: "Hello" } } } }
React's reconciliation engine — a task manager for incremental rendering.
Fiber replaced React 15's stack reconciler. Each component gets a Fiber node that tracks its own work, state, and DOM connection. React can pause, resume, and prioritize updates instead of blocking the main thread synchronously.
// Fiber Node (simplified) { type: Component, stateNode: DOMNode, // link to real DOM props: {}, child: FiberNode, // first child sibling: FiberNode, // next sibling return: FiberNode, // parent flags: Bitmask, // work to do (placement, update, deletion) updateQueue: [], memoizedState: {}, // hooks live here memoizedProps: {} // previous props }
Stack vs Fiber
Blueprint vs contractor — two layers, different jobs.
| Aspect | Virtual DOM | Fiber Node |
|---|---|---|
| Purpose | UI snapshot in memory | Task manager for updates |
| Type | Plain JS object | Internal React object |
| Granularity | Whole tree output | Per-component tracking |
| Rendering | Synchronous (React 15) | Incremental (React 16+) |
| Flexibility | Fixed comparison | Pause / resume / prioritize |
| Analogy | Blueprint | Contractor |
Render = planning. Commit = execution.
Phase 1 — Render
Phase 2 — Commit
Interview one-liner
How React finds the minimal set of DOM changes.
Reconciliation compares the previous and next Virtual DOM trees. The diffing algorithm applies heuristics to keep comparison at O(n) instead of comparing every possible tree transformation.
| Rule | Behavior |
|---|---|
| Element types differ | Destroy old subtree → create new |
| Element types same | Update changed props only |
| Component type same | Keep instance, update props/state |
| Component type different | Unmount old → mount new instance |
| Array children with keys | Match by key — efficient reorder |
| Array children without keys | Re-render all — inefficient |
// Old <div> <span key="1">A</span> <span key="2">B</span> </div> // New (reordered) <div> <span key="2">B</span> <span key="1">A</span> </div> // React diff: reorder only — not destroy + recreate both
Stable identity lets React diff arrays in O(n) instead of O(n²).
// Bad — no keys {todos.map((todo) => ( <Todo>{todo.text}</Todo> ))} // Good — stable unique keys {todos.map((todo) => ( <Todo key={todo.id}>{todo.text}</Todo> ))}
Interactive · List keys & reconciliation
With stable keys, each todo keeps its own state when the list reorders.
| Scenario | Without keys | With keys |
|---|---|---|
| Reorder list | Re-render all items | Move existing nodes |
| Insert item | Re-render from insertion point | Insert + minimal updates |
| Performance | O(n²) worst case | O(n) |
| Key source | Index (unstable on reorder) | Stable id (todo.id) |
Rule of thumb
Stored as a linked list on each Fiber node's memoizedState.
function Counter() { const [count, setCount] = useState(0); const [name, setName] = useState("John"); useEffect(() => { console.log("count changed:", count); }, [count]); return <div>{count}</div>; }
// Internal storage on Fiber node FiberNode { memoizedState: { baseState: 0, // useState(count) queue: { pending: [] }, next: { baseState: "John", // useState(name) queue: { pending: [] }, next: { // useEffect hook node deps: [count], next: null } } } }
What happens when you call setState or a state updater.
onClick={() => { setCount(1); setName("John"); setColor("red"); }} // React 18: all three batched → single render
| Before React 18 | React 18+ | |
|---|---|---|
| Event handlers | Batched | Batched |
| Promises / setTimeout | Multiple renders | Batched |
| Native events | Multiple renders | Batched |
| Result | Inconsistent batching | Automatic batching everywhere |
Why batching matters
Incremental rendering with priority — non-urgent work can wait.
Concurrent rendering is not a separate API — it is how React 18 schedules Fiber work. Urgent updates (typing, clicking) interrupt non-urgent work (filtering a large list, fetching data for a secondary panel).
import { useTransition } from "react"; const [isPending, startTransition] = useTransition(); startTransition(() => { setSearchTerm("new value"); // non-urgent — can be interrupted });
| Reconciler | Rendering | Granularity | Features |
|---|---|---|---|
| Stack (React 15) | Synchronous | Whole tree | Basic updates only |
| Fiber (React 16+) | Incremental | Per-component | Suspense, Transitions, Streaming |
One table to review before your interview.
| Concept | Description | Key Benefit |
|---|---|---|
| Virtual DOM | In-memory UI representation | Fast — no real DOM ops during diff |
| Fiber | Task manager for updates | Incremental, interruptible rendering |
| Reconciliation | Compare VDOM trees | Find minimal changes |
| Diffing | O(n) tree comparison heuristics | Efficient updates |
| Render Phase | Pure, interruptible planning | Non-blocking main thread |
| Commit Phase | Sync DOM mutation + effects | Consistent UI snapshot |
| Keys | Stable list item identity | Efficient reorder O(n) |
| Hooks | Linked list on Fiber | Ordered state per component |
| Concurrent Mode | Priority-based scheduling | Responsive under load |
| Automatic Batching | Coalesce setState (React 18) | Single render per tick |
Common interview questions about React internals.