Core Concepts8 min read · Updated 2025-06-01

React Virtual DOM & Reconciliation: How React Decides What to Render

Understand exactly how React's Virtual DOM, diffing algorithm, and Fiber reconciler work — and why keys are the most important prop you can give a list item.

💡 Practice these concepts interactively with AI feedback

Start Practicing →

React Virtual DOM & Reconciliation

What is the Virtual DOM?

The Virtual DOM is a lightweight JavaScript object tree that mirrors the structure of the real DOM. React keeps two versions: the current tree and the work-in-progress tree.

// JSX like this:
<div className="card">
  <h1>{title}</h1>
  <p>{body}</p>
</div>

// Becomes a plain JS object: { type: 'div', props: { className: 'card' }, children: [ { type: 'h1', props: {}, children: [title] }, { type: 'p', props: {}, children: [body] } ] }

The Virtual DOM's value: comparing two JS objects is orders of magnitude faster than querying and updating the real DOM. React diffs the objects, then surgically updates only what changed.

Reconciliation: React's Diffing Algorithm

When state or props change, React creates a new Virtual DOM tree and diffs it against the previous one. The diffing uses two heuristics to keep it O(n) instead of O(n³):

Heuristic 1: Different component types produce different trees

// Before:
<div><Counter /></div>

// After: <span><Counter /></span>

Different type (divspan) = destroy the old tree entirely, build a new one from scratch — even if children are identical. React never tries to "convert" one element type to another.

Heuristic 2: Keys identify list items across renders

// Before:
<li key="a">Alice</li>
<li key="b">Bob</li>

// After (item inserted at top): <li key="c">Carol</li> // React: new key "c" → INSERT <li key="a">Alice</li> // React: key "a" unchanged → MOVE <li key="b">Bob</li> // React: key "b" unchanged → MOVE

With keys, React performs 1 insert + 2 moves. Without keys, it would update every item's text and add one at the end — O(n) DOM mutations instead of the minimum needed.

The Key Bug (Most Common Interview Question)

// ❌ Using index as key — wrong when list order can change
{todos.map((todo, index) => (
  <TodoItem key={index} todo={todo} />
))}

// If you delete item at index 0: // - React sees: key=0 now has different data // - It UPDATES all items instead of removing the first one // - Input fields, focus, and animations break

// ✅ Use stable, unique IDs {todos.map(todo => ( <TodoItem key={todo.id} todo={todo} /> ))}

Index as key is only safe when the list is static and never reordered, filtered, or sorted.

Render Phase vs Commit Phase

Reconciliation has two distinct phases:

Render Phase (pure, interruptible)

  • React calls your component functions
  • Diffs the Virtual DOM trees
  • Computes the list of changes (effect list)
  • Can be interrupted, paused, and restarted (with concurrent features)
  • No DOM mutations happen here

Commit Phase (synchronous, uninterruptible)

  • React applies the computed changes to the real DOM
  • Runs refs and layout effects (useLayoutEffect)
  • Browser paints the screen
  • Runs passive effects (useEffect)

This separation is why getDerivedStateFromError (render phase) must be pure while componentDidCatch (commit phase) can have side effects.

Fiber: The Modern Reconciler

React Fiber (introduced in React 16) reimplemented the reconciler as a singly-linked list of work units called fibers. Each fiber represents one component.

Old reconciler: recursive, couldn't be paused. If a large component tree took 50ms to reconcile, the main thread was blocked for 50ms — causing dropped frames.

Fiber reconciler: breaks work into small units. React can process one fiber, check if the browser needs to handle input, and pause reconciliation to stay responsive. This enables:

  • useTransition — mark updates as interruptible
  • useDeferredValue — delay expensive re-renders
  • Suspense — pause rendering while data loads

Why shouldComponentUpdate and React.memo Exist

React re-renders a component whenever its parent re-renders, even if the component's props haven't changed. React.memo and shouldComponentUpdate short-circuit reconciliation: if props are shallowly equal, skip the render and use the previous Virtual DOM tree.

// Without memo: renders every time parent renders
function StaticWidget({ label }) {
  return <div>{label}</div>
}

// With memo: only renders when label changes const StaticWidget = React.memo(function({ label }) { return <div>{label}</div> })

Practice Virtual DOM and reconciliation questions at [JSPrep Pro](/auth).

📚 Practice These Topics
Rendering reconciliation
4–8 questions
React Fiber
4–8 questions
Rendering & Performance
4–8 questions
React Virtual DOM
8–12 questions

Put This Into Practice

Reading articles is passive. JSPrep Pro makes you actively recall, predict output, and get AI feedback.

Start Free →Browse All Questions

Related Articles

Deep Dive
We Built a RAG-Powered AI Question Engine Into a JavaScript Interview Platform — Here's Exactly How It Works
12 min read
Build Systems
Monorepo with Turborepo vs Nx: The Complete Comparison (2025)
9 min read
Core Concepts
map() vs forEach() in JavaScript: Which One to Use and Why It Matters
7 min read