skip to content

Keys & Instance Identity

How a runtime matches the children it rendered last time to the ones it renders now — by position or by a key — and what state, focus and host nodes follow. A staple list-bug question.

on this pageshow

questions

5

When a component framework re-renders a list of children, what does giving each child a key taken from the data do?

level: juniorimportance: must knowfreq 84%

answer

  1. identity, not ordering
  2. same item or same slot
  3. position is the default pairing
  4. state follows the key, not the index

basics

~20 s

A key tells the runtime which previously rendered child each new child is. Without one it pairs children by position, so inserting, removing or reordering items leaves per-item state, focus and host nodes behind at the old slot.

solid answer

~50 s

Every update starts with a pairing step: for each child described now, which child from last time is the same item? With no key the runtime pairs by position — first with first, second with second — trimming or creating at the end. A key supplied from the data (a record id) lets it pair by equal keys instead, wherever each item now sits. The pairing matters because a paired instance is updated in place: its inputs are refreshed, but its own state, its focus and text selection, an uncontrolled field's text, a scroll offset and a running animation all stay with that instance. Key correctly and those follow the item; key by position and they stay with the slot while only the data moves. Keys need to be stable, taken from the data, and unique among siblings — not across the page.

go deeper

for a junior

Be able to say it in one line: a key tells the runtime which item each child is, so per-item state and focus follow the item instead of the screen position. Take it from the data, keep it stable, unique among siblings.

for a middle

Explain the default. With no key the runtime pairs children by position and trims or creates at the end, so an insert or a reorder re-feeds existing instances with other items' data. Name what survives a pairing: state, focus, uncontrolled text, scroll, animations.

for a senior

Show that you know which lists are actually at risk — those whose rows own state, inputs, focus or animation — and that you check the identity source, not just the presence of a key. A key drawn from an editable field is a defect in waiting.

for a principal

Frame it as a data contract: most index keys exist because the payload carries no stable id. Deciding that every collection crossing a boundary ships an identity removes the whole bug class instead of policing each list call site.

## The question every update has to answer A component framework does not rebuild the screen from scratch on every update. It holds two things: a record of the children it built last time — each with its own state, its host nodes, and whatever is attached to them — and a fresh description of the children it wants now. Before it can compare any child's content, it has to pair them up: **for each new child, which old child, if any, is the same item?** That pairing is *identity matching*, and everything else in the update rides on it. ## Two ways to pair, and only one knows about items - **By position.** New child 1 pairs with old child 1, new child 2 with old child 2, and so on. If the new list is shorter, the leftover instances at the end are destroyed; if it is longer, new instances are created at the end. This is the default when the author gives the runtime nothing else to go on. - **By key.** The author labels each child with a value from the data, typically a record id. The runtime pairs old and new children whose keys are equal, wherever each one now sits; a key it has never seen means create, and a key that has disappeared means destroy. So a key is not a rendering hint and not a performance switch. It is the answer to *which item is this?* ## What the pairing decides Once a new child is paired with an old instance, that instance is updated in place: it receives the new inputs and re-renders. Anything it holds that is **not** recomputed from its inputs stays exactly where it is: - the component's own state — an expanded flag, a draft value, a selected tab; - host-node state the runtime never wrote: focus and text selection, an uncontrolled field's current text, a scroll offset, a media element's playback position; - a running transition or animation on that node; - handles other code holds to that node; - work the instance already started, such as an in-flight request. The pairing also decides the host work: a paired child's node is updated and, where the order changed, **moved**; an unpaired new child means a node is **created**; a leftover instance means its node is **destroyed**. ## Where positional pairing breaks | Change to `[A, B, C]` | Paired by position | Paired by key | |---|---|---| | Append `D` | `A`, `B`, `C` reused, `D` created — correct either way | the same | | Insert `Z` first | `A`'s instance now renders `Z`, `B`'s renders `A`, `C`'s renders `B`, and a fourth instance is created; every row's own state is one slot off | `Z` gets a new instance; `A`, `B` and `C` keep theirs and are moved | | Reorder to `C, A, B` | three instances stay put and are re-fed other items' data; nothing per-row travels | the three instances follow their items | | Remove `B` | `B`'s instance now renders `C`, and the last instance — the one that rendered `C` — is destroyed | `B`'s instance is destroyed; `A` and `C` keep theirs | Notice that positional pairing is not *wrong* in the append case. It breaks exactly when the list changes shape anywhere but the end, and it only produces a visible bug when the children own something that should travel. ## What makes a value usable as a key - **From the data, not from the render.** A record id. Not the item's index, and never a value produced while rendering. - **Stable.** The same item keeps the same key across updates, reorders and refetches. - **Unique among siblings.** Keys are compared inside one list only, so two unrelated lists on the same screen may both use `1, 2, 3`, and a nested list has its own scope. - **Cheap to compare.** Runtimes generally use a simple equality check, so a plain string or number is the safe choice. - **Present on every child.** A child without one falls back to being paired by position. In most frameworks the key is consumed by the parent's pairing step and is not delivered to the child as an ordinary input, which is why the child cannot read it and why it plays no part in the child's own comparison of old and new inputs. If the child needs the id, pass it again as a normal input. ## Reactivity models differ in what happens next, not in the pairing A runtime that re-runs the component function on each update uses the pairing to decide which previous instance to re-run and which subtree to compare. A runtime with fine-grained tracking uses it to decide which row's reactive cells and nodes survive, rather than which subtree to diff. A compile-time runtime bakes a keyed-list update path into generated code. All three answer the same question with the key, and in all three a wrong answer moves state to the wrong item. ## The practical rule Key a list by the item's identity from the data. Keying by where an item happens to sit only tells the runtime what it already assumed.

  • Can the child component read its own key as an input?
    In most frameworks, no. The key is consumed by the parent's pairing step, so the child never receives it among its ordinary inputs, and it plays no part in the child's own old-versus-new input comparison. If the child needs the identifier for a link or a request, pass it a second time as a normal input.
  • Does keying a list make it faster, or only more correct?
    Correctness is the reason; speed is a side effect. Keys let the runtime move an existing instance and its node instead of tearing down and rebuilding, which is cheaper on a reorder. But the bug keys prevent is state, focus and animations landing on the wrong item — a list of stateless text rows is keyed for the cheaper move, not to avoid that bug.
  • What happens when two siblings in one list carry the same key?
    Pairing becomes ambiguous: one old instance is claimed twice, or one new child finds nothing to pair with. Runtimes typically warn and then behave unpredictably — a row silently missing, or two rows sharing one instance's state. Treat a duplicate key as a data bug: the field chosen is not an identity.

Numbered seats versus named tickets. If the usher only knows seat numbers, removing one guest shifts everyone up a seat and the coats left on the chairs end up with the wrong people; names let each guest keep their own coat wherever they move.

saying these in an interview costs you the question

  • Calls a key a rendering optimisation rather than an item's identity
  • Thinks keys must be unique across the whole page, not among siblings
  • Reaches for the array index because every item has one
  • Expects the child to receive its key as an ordinary input
  • Believes a list without keys refuses to render at all
open as a page

When a list matched by position has a middle item removed, which per-item state ends up on the wrong row and why?

level: middleimportance: must knowfreq 76%

basics

~20 s

Everything not recomputed from inputs: the row's own state, focus and caret, an uncontrolled field's text, scroll offset and running animations. Positional matching re-feeds each instance with the next item's data and destroys the last instance, not the removed one's.

open as a page

What properties must a list key have to be usable, and which common key sources fail them?

level: middleimportance: should knowfreq 66%

basics

~20 s

A key must come from the data, stay the same for an item across updates, be unique among its siblings, and compare cheaply. The index, a value generated while rendering, a duplicated field and an editable field each fail one.

open as a page

A detail panel keeps the previous record's unsaved edits when a new record is selected; how does changing its key fix that, and what does it cost?

level: seniorimportance: should knowfreq 58%

basics

~20 s

The panel sits in the same tree position, so it is paired with the same instance and only its inputs refresh. Keying it by the record id makes each record a different child, discarding the old instance and its subtree.

open as a page

Across many teams, some list primitives warn on a missing key and others map by position silently; how do you prevent identity bugs?

level: principalimportance: should knowfreq 44%

basics

~20 s

Treat item identity as a data contract, not a review habit: collections carry a stable id, shared list helpers require an identity function, automated checks ratchet violations down, and one test per stateful list asserts state follows the item.

open as a page