awesome-everything RU
↑ Back to the climb

Browser & Frontend Runtime

Reconciliation: diffing heuristics and the key trap

Crux React reduces O(n³) tree diffing to O(n) with two heuristics — same type keeps state, different type destroys it. Keys identify list items across renders: index keys silently corrupt uncontrolled state on reorder.
Your altitude — climbing toward senior
ZeroJuniorMiddleSenior
You are at middle altitude — in the sky
◷ 14 min

A checkbox checked on row 3 stays checked after you delete row 1 — now appearing on what used to be row 4. No React warning. No console error. One line change — key={index} to key={item.id} — and the bug disappears. Keys are not hints. They are the mechanism React uses to pair fibers with items across renders, and getting them wrong corrupts state silently.

Reconciliation: the diffing rules. Reconciliation compares the new element tree against the current fiber tree to decide what to keep, update, or replace. A full tree diff is O(n³) in the general case; React makes it O(n) with two heuristics.

Heuristic 1 — same type, same fiber. If a <div> stays a <div>, or <ComponentA> stays <ComponentA>, React updates the existing fiber in place — patching its props, running reconciliation for its children. The fiber keeps its state.

Heuristic 2 — different type, full replacement. If a <div> becomes a <span>, or <ComponentA> becomes <ComponentB>, React tears down the whole subtree (unmounting it, destroying its state) and builds the new one fresh. It does not try to diff across types. This is why conditionally rendering two different components in the same slot resets all state — even if the new component looks similar.

Keys identify list children across renders. Within a list, React matches old and new children by their key, not their position. A stable key tells React “this is the same logical item, even if it moved.” This is the entire reason keys exist. React builds a map from key to old fiber, then walks the new children matching each to its old fiber by key. The matched fiber carries forward everything stateful: useState values, useRef contents, uncontrolled DOM state (an input’s typed text, a checkbox’s checked-ness, scroll position, focus), and the actual DOM node.

key={index} vs key={id} — what happens on delete
key={index} (broken)
Before: key=0 → item A ✓checked
Before: key=1 → item B
Before: key=2 → item C
Delete item A
After: key=0 → item B (but fiber from item A — ✓checked!)
After: key=1 → item C
key={item.id} (correct)
Before: key=“a” → item A ✓checked
Before: key=“b” → item B
Before: key=“c” → item C
Delete item A
After: key=“b” → item B (correct fiber, no state)
After: key=“c” → item C

Why key={index} is a trap. With key={index}, the key is the position, so when the list reorders or an item is inserted at the front, every item’s key shifts. React then thinks item-at-index-0 is “the same item” even though the data moved — it keeps the old fiber’s state and DOM but feeds it the new item’s props. The visible bug: a checkbox checked on row 3 stays checked after you delete row 1, now attached to what used to be row 4.

Controlled props update correctly (they are passed fresh each render); uncontrolled state does not (it lives on the fiber). key={index} is safe only for a list that is static and never reorders; for anything dynamic it silently corrupts state.

Edge cases

Math.random() as a key is worse than key={index}. It assigns a new key every render, so React always sees a “different” item at every position — it unmounts and remounts every list item on every update. You get correct state (because the fiber is always new) but you pay for an unmount + mount of every item on every keystroke. key={index} is wrong for dynamic lists; key={Math.random()} is wrong for all lists.

Quiz

A list of items renders with `key={index}`. You delete the first item. A checkbox that was checked on the old second item is now checked on the wrong row. Why?

Quiz

You render `condition ? <ComponentA /> : <ComponentB />` in the same slot. When condition flips, what happens to ComponentA's state?

Quiz

Why does controlled state (value, checked passed as props) look correct with `key={index}` while uncontrolled state (typed text, scroll position) does not?

Recall before you leave
  1. 01
    Explain, at the fiber level, why key={index} corrupts state when a list reorders.
  2. 02
    What does React do when a component type changes in the same slot?
  3. 03
    When is key={index} actually safe?
Recap

Reconciliation reduces O(n³) tree diffing to O(n) with two heuristics: same element type means update the fiber in place (state preserved); different type means tear down the subtree and rebuild fresh (state destroyed). For lists, React uses key to match new children to old fibers by identity, not position. A fiber carries everything stateful — useState values, refs, uncontrolled DOM state — so pairing the wrong fiber to the wrong item corrupts that state silently. key={index} is the trap: index keys are positional, so any reorder, insert, or delete shifts which fiber an item gets. Use a stable identity key (database id, slug) whenever the list is dynamic.

Connected lessons
appears again in143
Continue the climb ↑Priority lanes, time-slicing, and useTransition
shortcuts expand
search
K
prev piece
k
next piece
j
cycle tier
t
this menu
?
sources3
expand
  1. 01
  2. 02
  3. 03

Trademarks belong to their respective owners. Editorial reference only.