skip to content

Keys and List Reconciliation

Keys tell React which item in a list is which across renders; get them wrong and you get state landing on the wrong row, lost input values, or needless DOM work. The flip side is a favourite trick question — deliberately changing a key to reset a component's state.

part ofReactoverview, primer and where to startread it →
on this pageshow

explore

questions

5

When a React parent re-renders a list of children, how does the reconciler decide which fiber from the previous render corresponds to each new element — with keys and without them?

level: middleimportance: must knowfreq 70%

answer

  1. two sequences, one question per element
  2. position is the fallback identity
  3. fast parallel walk, then a map
  4. key compared before element type
  5. leftovers in the map get deleted

basics

~20 s

Without keys, React pairs children by position: slot one with slot one. With keys, it pairs by key, walking both lists in parallel while the keys agree and then looking up the remaining previous children in a key-based map.

solid answer

~50 s

React reconciles a parent's children as two lists. Without keys the pairing is purely positional — the element at index 2 is matched with whatever fiber sat at index 2 last time. With keys, React first walks both lists in parallel while the keys line up, which covers the common case of an unchanged list or an append cheaply. At the first key mismatch it stops, collects the remaining previous children into a map keyed by their keys, and looks up each remaining new element there. A hit reuses that fiber, possibly marking it to be moved in the DOM; a miss creates a new fiber; anything left in the map at the end is deleted. Crucially the key is checked *before* the element type, so a matching key with a different type still means no reuse. Reuse means the existing fiber and its hook state and DOM node are kept and updated in place.

go deeper

for a junior

Recall that keys tell React which element is which between renders, and that with no key React falls back to matching by position in the list.

for a middle

Walk the pairing explicitly: parallel walk while the keys agree, then a map of the remaining previous children, hits reused, misses mounted, leftovers deleted. Say that the key is compared before the element type.

for a senior

Connect the mechanism to what survives: a reused fiber keeps its hook state, refs, DOM node, focus and uncontrolled values, and a move is an insertion rather than a rebuild. Use that to explain real list bugs you have debugged.

for a principal

Own the tradeoff behind the design: React chose a linear heuristic with a caller-supplied identity hint over an exact tree diff. Be ready to argue what that buys, and where handing identity to the caller becomes a liability at scale.

## The problem the reconciler is solving Rendering a list gives React two flat sequences of elements: the children this parent produced last render, and the children it just produced. It has to answer one question per new element — "which of the old ones are you?" — because the answer decides whether an existing fiber (with its hook state, its refs, its real DOM node) is updated in place, or thrown away and rebuilt. A true minimal-edit-distance tree diff is too expensive to run on every keystroke, so React uses a linear pass with one hint from you: the key. ## Without keys: pairing by position If the children carry no key, React treats position as identity. New child at index *i* is paired with the previous child at index *i*. If they are the same element type, the fiber is reused and its props are updated; if the type differs, that fiber is deleted and a new one is created for the slot. Extra new children at the end are mounted; leftover previous children at the end are deleted. This is exactly what array-index keys do, which is why index keys and no keys behave identically apart from the development warning. ## With keys: pairing by name With keys, the pass has two stages. **Stage one — walk in parallel.** React steps through both lists together. As long as the new child's key equals the old child's key at the same position, it pairs them immediately. For a list that did not change, or that only had items appended or had props updated in place, the whole reconciliation is this cheap linear walk and nothing else runs. **Stage two — the map.** At the first position where the keys disagree, the parallel walk stops, because from here on positions no longer correspond. React puts every remaining previous child into a map keyed by its key (children without a key are stored under their index). Then it continues through the remaining new elements, looking each one up in that map: - **Hit** — the previous fiber is pulled out of the map and reused. React also tracks how far along the previous list it has already placed things; if this fiber came from an earlier position than that watermark, it is flagged so the commit phase moves its DOM node with an insertion, rather than recreating it. - **Miss** — no previous child owned that key, so a new fiber is created and mounted. Whatever is still in the map when the new list is exhausted was not claimed by anyone, so those fibers are deleted: their effect cleanups run, their refs are detached, their DOM nodes are removed. ## Key is checked before type This ordering surprises people. React first asks "do the keys match?" and only then "is it the same element type?". Two consequences: - Same key, different type (`<Row key="a" />` becomes `<Card key="a" />`): the fiber is not reused. The old one is deleted, a new one is mounted. - Different key, same type: also no reuse. The key mismatch alone is enough to discard the fiber, even though the component is identical. ## What "reuse" actually means Reusing a fiber is not a shallow optimisation — it is what makes a component feel continuous: - the fiber's hook state list survives, so `useState` values, `useRef` boxes and reducer state are exactly what they were; - the host DOM node survives, so uncontrolled input values, focus, text selection, scroll position and running CSS transitions survive; - effects are not torn down; they re-run only if their dependencies changed. Moving a reused fiber is cheap too: the commit phase performs an insertion of the existing node at the new position. Nothing is re-created. ## Why the hint has to come from you React cannot infer identity from the data — it never sees your objects, only elements. The key is the one place you tell it "this element is the same thing as the one that had this name last time". Everything else in list reconciliation follows mechanically from that one input, which is why a key derived from something unstable poisons the whole process. ## Answering well Name the two stages (parallel walk, then map lookup), say that the key is compared before the type, and finish with what reuse buys — fiber state and DOM node preserved, versus delete-and-mount when nothing matches.

  • Why does React bother with the parallel walk instead of going straight to the map?
    Because the overwhelmingly common cases — an unchanged list, or one with items appended, or one where only props changed — resolve entirely in that walk with no allocation. The map is built only from the point where the two lists stop agreeing, so the extra cost is paid only by lists that were actually reordered or spliced.
  • When a reused fiber has to change position, does React recreate its DOM node?
    No. The fiber is flagged for placement, and the commit phase inserts the existing DOM node at its new position. The node, its uncontrolled input value, and any attached listeners are preserved — moving is an insertion, not a rebuild.
  • What happens when the key matches but the element type has changed?
    No reuse. React compares the key first and the type second, so a matching key gets you as far as the type check; a different type then means the old fiber is deleted — cleanups run, refs detach — and a fresh fiber is mounted for the new type with new state.

saying these in an interview costs you the question

  • React diffs the real DOM trees to find the changes
  • Keys let React skip re-rendering unchanged children
  • Without keys React falls back to comparing props
  • Matching keys reuse the fiber even when the type changed
  • Reordering a keyed list recreates the DOM nodes

context

open as a page

In React, a component of the same type is rendered with key="a" on one render and key="b" on the next, with identical props. What does the reconciler do to its fiber, its hook state, its DOM node and its effects?

level: middleimportance: must knowfreq 58%

basics

~20 s

The old fiber cannot be reused, so React deletes it and mounts a new one: effect cleanups run, refs detach, the DOM node is removed, and the replacement mounts with fresh hook state and new DOM. The whole subtree is rebuilt.

open as a page

A React list keyed by array index has a new item prepended. Trace what the reconciler does: which fibers are reused, which DOM nodes are created or moved, and where each row's hook state ends up? Contrast that with the same list keyed by a stable item id.

level: seniorimportance: must knowfreq 55%

basics

~20 s

With index keys every slot still matches, so React reuses all fibers and patches each one with the next item's data, appending one new fiber at the end. Hook state stays with the position, not the item. Stable ids instead insert one node and leave the rest untouched.

open as a page

In a React app, two unrelated lists on the same page each give their rows the key values 1, 2 and 3. Is that a problem? Explain the scope in which React actually compares keys.

level: juniorimportance: should knowfreq 45%

basics

~10 s

No. React compares keys only among the children of one parent, during that parent's re-render. Separate lists may reuse the same key values freely; only duplicate keys inside a single list are a bug.

open as a page

A React list is rendered with key={crypto.randomUUID()} generated inside the render. What does the reconciler do on every re-render of that list, and why is this worse than keying by array index?

level: middleimportance: should knowfreq 40%

basics

~20 s

Every key is new, so no previous child matches: React deletes every existing fiber and mounts the whole list fresh on each render. Index keys at least pair positionally and reuse most fibers, so they are strictly cheaper.

open as a page