codejump

AcademyReact

Reconciliation and Why Keys Matter

The heuristic React uses to decide what to reuse and what to rebuild, and why the wrong key silently attaches state to the wrong row.

4 min readUpdated

3 exercises

Waiting at the end of this page.

The heuristic, not the algorithm

A generic tree-diff is expensive — comparing two arbitrary trees of N nodes is O(n^3) in the general case. React does not attempt it. Instead it applies one heuristic, applied top-down, sibling by sibling:

  • Same element type at the same position → keep the underlying instance, patch its props, recurse into its children.
  • Different element type at the same position → tear the old instance down completely (including its state) and mount a fresh one in its place.
JSX
// Render 1
<UserCard user={a} />
// Render 2
<UserCard user={b} />
// Same type, same position → the same UserCard instance is reused,
// only its props change. Any internal state (open menus, scroll position
// inside the card) survives, now describing user b.
JSX
// Render 1
<UserCard user={a} />
// Render 2
<AdminCard user={a} />
// Different type at the same position → UserCard is unmounted, its
// state is gone, and a brand new AdminCard mounts from scratch.

This is what "component instance" means in React: not the element (a new one is created every render) but the thing React keeps alive across renders because the heuristic said to reuse it.

Where keys come in

The heuristic above works fine for a fixed, ordered set of children. It breaks down for a list, because "position" stops meaning anything useful the moment the list can be reordered, filtered, or have an item removed from the middle.

key is how you opt out of "position decides identity" and tell React what actually decides identity: not where an item sits, but which item it is.

JSX
{todos.map((todo) => (
  <TodoRow key={todo.id} todo={todo} />
))}

With a stable key, React matches old children to new children by key, not by index. Delete the second todo, and React now sees "the row keyed 2 is gone, the rows keyed 1 and 3 are unchanged" — it unmounts exactly one row and leaves the other two, and their DOM nodes and internal state, completely alone.

The array-index bug

Using the array index as the key looks harmless and is the single most common React interview trap, because it reintroduces the exact problem key exists to solve: index is still position.

JSX
// Each row has its own local "editing" state
function TodoRow({ todo }) {
  const [editing, setEditing] = useState(false);
  return editing ? <TodoEditor todo={todo} /> : <TodoLabel todo={todo} onClick={() => setEditing(true)} />;
}

// BAD: key is the index
{todos.map((todo, i) => <TodoRow key={i} todo={todo} />)}

Put row 0 into edit mode, then delete row 0 from the underlying array. The remaining first item shifts up to index 0. React compares "the thing keyed 0" before and after — same key, same position — and reuses the same TodoRow instance, patching its todo prop to the new first item. But editing is local state on that instance, and it never got reset. The wrong todo — the one that shifted into position 0 — now renders open in edit mode.

JSX
// GOOD: key is the thing's own identity, independent of where it sits
{todos.map((todo) => <TodoRow key={todo.id} todo={todo} />)}

Now deleting the edited row deletes the instance keyed to it, editing state and all, and nothing shifts into a state it never had.

What a key actually needs to be

RequirementWhy
Stable across rendersRegenerating it every render (key={Math.random()}) defeats the purpose — every item looks "new," so nothing is ever reused
Unique among siblingsReact only compares keys within one list; the same key in two different lists never collides
Not necessarily globally uniqueA todo's id and a comment's id can both be 1 — they live in different lists
Not the array index, unless the list is truly staticStatic and never reordered/filtered is the only case index is safe

The index rule of thumb: if the list can change shape — reorder, insert, delete, filter — the index is not a safe key, because it stops describing the item and starts describing a slot.

Now practice it

2 challenges · 1 quiz

Reading this page is the cheap half. These are the exercises that make you use it.