Beyond Code & Karma · React Internals
You never tell React how to change the DOM — you declare the
tree you intend, and React reconciles it with what's
already mounted, committing only the necessary
operations. That is the whole trick behind the virtual DOM, and the
single reason key exists.
Chapter 01 — The Foundation
When a component's state or props change, React re-runs it and gets a fresh tree of elements — a lightweight, in-memory description of the UI, the virtual DOM. The real browser DOM is slow to mutate, so React never applies the new tree blindly. It diffs the new tree against the previous one, computes the smallest set of real DOM edits, and commits only those. That diff-and-commit cycle is reconciliation.
The Cosmic Connection
In Vedic thought Vishnu is the preserver, and the cosmos is his lila — the divine play of endless forms arising and dissolving. The UI is exactly this: a component re-renders without end, a new tree every tick, the play of forms. Yet identity persists through the churn — and the thread that preserves it is the key. Reconciliation is the dharma that reuses the same node rather than destroying and recreating it. An unstable index key is forgetting true identity — needless death and rebirth.
"The forms play and dissolve without end; give each its true name, and what is real is preserved across the churn."
Chapter 02 — Show Me The Code
React never proves it found the minimal edit script — that would be cubic. It makes two assumptions that hold in practice and collapse the cost to linear.
// Heuristic 1 — same type at the same slot: reuse, patch props. <div className="card"> → <div className="card hot"> // REUSE the DOM node, UPDATE one attribute. No remount. // Heuristic 2 — different type at the same slot: tear down + rebuild. <ProductCard /> → <Banner /> // UNMOUNT the old subtree, MOUNT the new one. State is lost.
<Input> for a <Spinner> in the
same position remounts — and resets — everything beneath it.
Children are matched in order by default. To match by
identity — so reorders, inserts, and deletes are understood
correctly — each child needs a stable key drawn from your
data, not from its position:
// Stable id as key → identity survives a reorder. function ProductList({ items }) { return ( <ul> {items.map((item) => ( <ProductCard key={item.id} // the node's true name name={item.name} /> ))} </ul> ); }
Using the array index as the key is matching-by-position wearing a disguise: the key is the slot. Reorder the list and every index shifts, so React reuses the wrong node for the wrong data — and any focus, selection, or animation stays welded to the slot:
// index as key → the key IS the position. // Reorder and every index shifts → React reuses the wrong node. {items.map((item, i) => ( <ProductCard key={i} // 🚩 forgets identity name={item.name} // data moves, node stays welded to slot i /> ))}
State changed → React re-runs the affected components and produces a fresh element tree in memory. No DOM is touched yet. This work is interruptible.
Walk old and new tree in lockstep. Classify each node: matched, props-updated, newly-mounted, or unmounted. The output is a minimal list of effects.
Apply the effect list to the real DOM in one synchronous batch — insert, remove, update text and attributes, run layout effects. Short list = fast frame.
Chapter 03 — Live Visualization
A small before/after diff, auto-playing. Same type at a slot is reused and patched; a changed type is remounted. Watch the classification change as the new tree mutates.
Chapter 04 — Deep Dive
This is where the 10,000 basic videos stop. Here's what actually decides whether your UI stutters.
| Situation | What React does | Cost & state |
|---|---|---|
| Same type, same slot | Reuse the DOM node, patch changed props only | Cheap · state preserved |
| Different type, same slot | Remount — unmount old subtree, mount new | Expensive · state destroyed |
| Keyed list, reordered | Move existing nodes to new positions | Cheap · state follows the item |
| Index-keyed list, reordered | Rewrite content in place at each slot | Wasteful · state welded to slot |
The general problem — find the minimum edit distance between two
arbitrary trees — is O(n³). React refuses to pay that. It assumes
(1) two elements of different type produce different trees, so it
never tries to morph a <div> into a
<span> — it replaces; and (2) children keep a
stable identity across renders via key. Those two
assumptions let it diff in a single linear pass. The cost of the
shortcut is precisely the bugs in this chapter: they're what
happens when reality violates an assumption.
Index keys don't usually look broken — the list renders the right text after a reorder. The damage is to anything the DOM node holds that React can't see in props:
| Lost to index keys | Why |
|---|---|
| Input focus & cursor | Focus lives on the DOM node; reusing it for different data points focus at the wrong row. |
| Uncontrolled input text | A half-typed value stays in the slot's node and is now shown for a different item. |
| CSS transitions in flight | The node isn't moved, just rewritten — so the move never animates. |
| Component state | State keyed to the node, not the data, follows the slot, not the item. |
The flip side of "stable keys preserve identity" is that
changing a key forces a remount. That's a feature: give a
component key={userId} and switching users blows
away all internal state and effects, giving you a guaranteed fresh
mount instead of a stale, half-updated component. It's the
idiomatic way to say "this is a different thing now, start over."
Reconciliation only runs where it must. If a component's props are
referentially equal and its state didn't change, React can bail out
and skip its subtree. React.memo,
useMemo, and stable callbacks exist to keep those
props referentially equal so the bailout fires. The common
performance bug isn't a slow diff — it's a new object or inline
function passed as a prop every render, defeating the bailout and
forcing needless reconciliation down the tree.
Under the hood the reconciler is Fiber: the render-phase work is broken into units React can pause, resume, and abandon, so a long render doesn't block the main thread. Concurrent features (transitions, Suspense) build on that. But the rules in this chapter are unchanged — same-type reuse, key identity, render then commit. Server Components shift where components render and how much JS ships; on the client the diff-and-commit cycle, and keys, matter exactly as much.
Chapter 05 — Dharma & The Diff
Declaring the new tree is sankalpa — a stated intention. The reconciler performs nishkama karma: right action without waste, without attachment to re-rendering for its own sake. It does not cling to the old form, and it does not rebuild the world. It acts only where action is required.
// The reconciler does not cling, and does not destroy in haste. diff(oldTree, newTree); // declare intention (sankalpa), see only the delta // What is unchanged is left untouched — reused, not reborn. // What truly differs is committed — nothing more (nishkama karma). commit(effects); // act only where action is required
What is unchanged is left untouched — reused, not reborn. What truly
differs is committed, and nothing more. Give each node its true name
— the key — and identity survives the churn of rebirth.
The diff is small, the frame is fast, and nothing is destroyed in
haste.
"Act only where action is required; reuse what endures, release only what has truly changed." — the Gita, interpreted for the render loop 🙏
Chapter 06 — Test Yourself
Three questions. No cheating. Your karma is watching.
{} or () => {} created inline every render breaks that equality, so memoized children re-reconcile needlessly. useMemo / useCallback / stable references restore the bailout.The Enlightenment
key={index} is matching by position in disguise. On reorder, state and focus stay welded to the slot, not the item.
कर्म करो, फल की चिंता मत करो