ESC

Type to search the knowledge base.

Reconciliation & the Virtual DOM

What re-render actually means, why keys exist, and when memo is a tool — not a personality trait.

intermediate3 min read
  • react
  • reconciliation
  • virtual-dom
  • performance

React is not a magic fast DOM. It’s a scheduler of UI descriptions.

You describe trees. React compares “what you want now” to “what’s already on screen,” then touches the real DOM as little as it can. That compare step is reconciliation. The “virtual DOM” is just the tree of those descriptions — not a second invisible browser living in your laptop.

If you only memorize “virtual DOM = fast,” you’ll over-memo everything and still ship jank.

Two phases (say this in interviews)

  1. Render / reconcile — pure-ish JS work. Build the next tree. In concurrent mode, this can pause.
  2. Commit — mutate the host tree (DOM), then run effects.

You can re-render a lot and barely touch the DOM if the output didn’t change. You cannot make commits free if you keep remounting half the page.

Elements are blueprints

const el = <Button onClick={save}>Save</Button>;
// roughly: { type: Button, props: { onClick: save, children: 'Save' } }

Next render, React walks old fiber vs new element:

  • Same type in the same spot → update that fiber
  • Different type → throw away the old subtree, mount a new one
  • Lists → match by key + type

Keys: identity, not a fashion accessory

// Bad when the list can reorder or insert
{items.map((item, i) => (
  <Row key={i} item={item} />
))}

// Good: stable id from your data
{items.map((item) => (
  <Row key={item.id} item={item} />
))}

Index keys + insert at front = every row looks “new.” Inputs glitch. Local state teleports. You’ve met this bug at 1am.

Keys must be stable, unique among siblings, and not “whatever the map index is today.”

Re-render ≠ paint

A component re-renders when:

  • its state changes
  • its parent re-renders (and usually passes props down)
  • context it reads changes
  • you remount it (key change)

That’s JS work. DOM work only happens in commit when host output actually changed.

function Parent() {
  const [n, setN] = useState(0);
  return (
    <>
      <button onClick={() => setN((x) => x + 1)}>{n}</button>
      <ExpensiveChild />
    </>
  );
}

ExpensiveChild re-renders with Parent unless you stop it (memo, state move, composition). memo is a scalpel. Measure first. Unstable props (style={{}}, inline functions) make memo useless and make you look busy.

Concurrent mode in one breath

React may start rendering, get interrupted, throw work away, start over. So render must be pure with respect to props/state. Side effects belong in effects / event handlers — not “while rendering, also write to a global store real quick.”

Practical rules that survive production

  1. Put state where it belongs — not always “as high as possible,” not always “as local as possible.”
  2. Fix list keys before you touch useMemo.
  3. Profile with React DevTools before decorating every file with memo.
  4. Split context if one high-frequency value is torching a wide tree.

Interview closer

“Virtual DOM is a tree of UI descriptions. Reconciliation diffs trees and schedules host updates. Keys preserve identity across list renders. Concurrent features make render interruptible, so purity matters.”

That’s the tweet-length version. The rest of this page is the receipts.

Related guides