Topics in this subject
React 6 min read Updated 6 Aug 2026

Rendering, Virtual DOM & Reconciliation

Render phase builds and diffs a Virtual DOM; commit phase applies minimal DOM mutations, with keys guiding reconciliation.

🧑‍🏫 Sabse pehle — simple mein samjho#

UI ek formula hai: UI = f(state). Jaise hi state badalti hai (jaise useState se count badhata hai), React dobara component chala ke naya UI banata hai — isko re-render kehte hain. Andar-andar React purane aur naye UI ko Virtual DOM mein compare karta hai (diff) aur screen pe sirf wahi cheez update karta jo actually change hui. Jaise report card mein sirf ek number badle to poora card dobara nahi likhte, sirf woh number theek karte ho.

function Counter() {
  const [count, setCount] = useState(0);
  // state badli -> React re-render karta -> sirf yeh number update hota
  return <button onClick={() => setCount(count + 1)}>Count: {count}</button>;
}

Yaad rakho: state badlo, React khud naya UI bana ke sirf zaroori part screen pe update kar deta hai.

Render phase vs commit phase 🎯#

React updates the screen in two distinct phases:

Phase What happens Side effects allowed?
Render React calls your components, builds a new Virtual DOM tree, diffs it against the previous ❌ Must be pure — no DOM writes, no mutations. Can be paused/aborted/restarted
Commit React applies the computed changes to the real DOM, then runs useLayoutEffect, then paints, then useEffect ✅ DOM is live

Because the render phase can run multiple times or be thrown away (concurrent features, Strict Mode double-invoke in dev), rendering must be a pure function with no side effects. 🟢

The Virtual DOM & diffing#

React keeps a virtual tree (plain JS objects). On update it builds a fresh tree and compares node-by-node against the old one, producing the minimal list of real DOM operations. A naive tree diff is O(n³); React uses heuristics to get O(n):

  1. Different element type → tear down the old subtree, build the new one (state is lost).
  2. Same type → keep the DOM node, update only changed attributes/props, recurse into children.
  3. Lists → use keys to match children across renders.
// type changed div→span: React destroys the div subtree and remounts
{isEditing ? <span>{x}</span> : <div>{x}</div>}

Worked example: editing a todo vs inserting one 🎯#

Take a two-item todo list and walk through two separate updates to see exactly what DOM work React does in each case.

const todosBefore = [
  { id: "a", text: "Buy milk" },
  { id: "b", text: "Pay electricity bill" },
];

// Update 1: user edits todo "b"'s text
const todosAfterEdit = [
  { id: "a", text: "Buy milk" },
  { id: "b", text: "Pay electricity bill by Friday" }, // only this field changed
];

// Update 2: user adds a new todo at the end
const todosAfterInsert = [
  { id: "a", text: "Buy milk" },
  { id: "b", text: "Pay electricity bill" },
  { id: "c", text: "Book cab to airport" }, // brand-new item
];

Because every <li> keeps the same key={todo.id} across renders:

  • Edit case — React matches key="a" and key="b" to their existing <li> DOM nodes (same type, same key), so it does not touch <li key="a"> at all, and for <li key="b"> it patches only the text node inside — a single, tiny DOM mutation. It never tears down or rebuilds the <ul>.
  • Insert case — React again matches key="a" and key="b" to their existing nodes untouched, and simply creates and appends one new <li key="c">. Nothing about the first two rows is re-created.

Compare that to what would happen without stable keys (or with index keys) on an insert-at-the-end: it happens to work out the same here because the change is at the end, but an insert at the front or middle would shift every index, forcing React to patch every row's content instead of just adding one — see the Lists & Keys topic for that failure mode in detail.

flowchart TD
  subgraph OLD["Old Virtual DOM"]
    OU["ul"] --> OA["li key=a: 'Buy milk'"]
    OU --> OB["li key=b: 'Pay electricity bill'"]
  end
  subgraph NEW["New Virtual DOM (edit + insert applied)"]
    NU["ul"] --> NA["li key=a: 'Buy milk'"]
    NU --> NB["li key=b: 'Pay electricity bill by Friday'"]
    NU --> NC["li key=c: 'Book cab to airport'"]
  end
  subgraph PATCH["Minimal patch React commits"]
    P1["li#a untouched, DOM node reused as-is"]
    P2["li#b: update text node only"]
    P3["li#c: create + append new li"]
  end
  OA -.->|"same key, same type, no change"| P1
  OB -.->|"same key, text changed"| P2
  NC -.->|"new key, no match in old tree"| P3

Reconciliation & the role of keys 🎯#

Reconciliation is the diffing algorithm. Within a list, React uses key to identify which items are the same across renders, so it can move/update rather than destroy and recreate. Keys must be stable, unique among siblings. Without them React falls back to index matching, causing state/DOM bugs on reorder or insert (see the Lists & Keys topic).

What triggers a re-render ⚠️#

A component re-renders when any of these happen:

  • Its state changes (setState from useState/useReducer).
  • Its props change (because its parent re-rendered).
  • A Context value it consumes changes.
  • Its parent re-renders — by default children re-render too, even if their props are identical.

⚠️ A parent re-render cascades to all descendants unless memoized. Use React.memo, and stabilize props with useMemo/useCallback, to prune unnecessary child renders. (Note: React 19's Compiler can auto-memoize, reducing manual work.)

React Fiber (brief)#

Fiber is React 16+'s reconciler — a reimplementation where the work of rendering is split into small units ("fibers") that can be paused, prioritized, and resumed. This enables concurrent rendering: React can interrupt a low-priority render to handle urgent updates (e.g. typing), then continue. It's the engine behind useTransition, Suspense, and time-slicing.

State change → screen#

flowchart LR
  S["state / props change"] --> R["RENDER phase: call components, build new VDOM"]
  R --> D["diff vs previous VDOM (reconciliation)"]
  D --> C["COMMIT phase: apply minimal DOM mutations"]
  C --> L["useLayoutEffect"]
  L --> P["browser paints"]
  P --> E["useEffect"]

Interview Q&A#

Q1. What is the difference between the render and commit phases? Render builds and diffs the Virtual DOM and must be pure (it can be interrupted/re-run). Commit applies the diff to the real DOM and runs effects; the DOM is live there.

Q2. Why must render be pure / side-effect free? React may call components multiple times or discard a render (concurrent mode, Strict Mode double-invoke). Side effects during render would run unpredictably. Put effects in useEffect.

Q3. What causes a component to re-render? Its own state change, changed props, a consumed Context value changing, or its parent re-rendering. By default children re-render when the parent does.

Q4. What is reconciliation and how do keys help? Reconciliation is React's diffing of old vs new Virtual DOM. Keys let React match list items across renders so it moves/updates existing nodes instead of destroying and recreating them.

Q5. What is Fiber and what does it enable? Fiber is React's reconciler that splits rendering into interruptible units of work, enabling concurrent features like prioritization, time-slicing, useTransition, and Suspense.