Across many teams, some list primitives warn on a missing key and others map by position silently; how do you prevent identity bugs?
answer
- silent, conditional, non-local
- identity is a data contract
- make the unkeyed call impossible
- assert state after a reorder
- ratchet, do not big-bang
basics
~20 sTreat 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.
solid answer
~50 sThe failure mode is what makes this organisational. Nothing crashes: a row's state lands on its neighbour, a save hits the wrong record, and the report arrives as flaky UI that closes unreproduced. Reviews are weak here because the key expression sits far from the stateful child. So I move the guarantee earlier. Make identity a data contract: every collection crossing a boundary ships a stable id, and client-created rows get one at creation — most index keys exist because there was nothing else to use. Make the unsafe call hard to write: a shared list helper whose identity argument is required. Automate what the framework will not warn about, escalate warnings to build failures, and ratchet existing violations down. Then template the one test that detects it: put state in a row, mutate the collection, assert where the state is — and spend that effort where rows own state.
go deeper
The takeaway at this level is the habit: when you render a collection, ask what identifies each item in the data. If the answer is nothing, that is worth raising rather than filling in with the position.
Be ready to explain why review and snapshot tests miss this class, and to write the test that does not: put state in a row, mutate the collection, assert where the state ended up.
Show you would fix the cause rather than the call sites — identity in the payload, a helper that requires it, warnings escalated to failures — and that you would target lists whose rows own state instead of policing every list equally.
The judgment is where the guarantee lives and what it costs. Contracts and required arguments survive turnover; memos do not. Be explicit about what you deliberately do not enforce, and about the ratchet that avoids a big-bang cleanup.
## Why this needs a strategy rather than a rule A list-identity bug has three properties that defeat ordinary quality practice. - **It is silent.** No exception, no failed request, no log line. The screen is plausible; it is just attributing state to the wrong item. - **It is conditional.** It appears only after a particular mutation — a prepend, a middle removal, a reorder, a filter keystroke — so it survives manual testing that adds items at the end. - **It is non-local.** The defect is a key expression on a list; the symptom is a wrong checkbox or a jumped caret several components deeper. Reviewers looking at the list see no bug, and reviewers looking at the row cannot see the key. The result in a large organisation is a steady trickle of reports — *the wrong row was selected*, *my edits went to another record* — that get triaged as unreproducible flakiness. Platforms differ in how loudly they help: some list primitives warn when identity is missing, some accept a positional mapping in silence, and a positional key such as an index silences even the ones that warn. You cannot rely on the framework to tell you. ## Levers, in order of leverage 1. **Make identity a data contract.** Require every collection that crosses a service or module boundary to carry a stable per-item identifier, and mint one at creation for rows that exist only on the client. This is the highest-leverage move because most index keys are not laziness; they are the absence of anything else to use. It also fixes every consumer of that payload at once. 2. **Make the unsafe call hard to write.** Wrap the platform's list primitive in a shared component or helper whose identity argument is **required**, accepting a field name or a function. An unkeyed list stops being a style choice and becomes something you cannot express. The cost is an internal abstraction to own, and the discipline not to let it drift from the platform's own capabilities. 3. **Automate what exists.** Turn available lint rules on, forbid positional and generated keys, and escalate framework warnings to build failures so they cannot accumulate. On a large codebase, ratchet rather than gate: freeze new violations immediately, burn down the existing list with a codemod plus owners, and publish the count so it trends. 4. **Test the property, not the markup.** Snapshot tests cannot see this bug, because the markup is correct. The test that catches it is short and mechanical: put state into a row (type into it, expand it, focus it), mutate the collection (prepend, remove the middle, reorder), then assert the state is still on the same item. Templating that test and requiring it for lists whose rows own state is the only check measured against the real failure. 5. **Route the symptom.** Teach support and QA the signature — wrong row, jumped focus, edits on a neighbouring record after a list changed — so those reports arrive pre-labelled instead of being closed as flaky. ## Where not to spend - **Do not mandate globally unique keys.** Uniqueness is required among siblings only. A global rule teaches a false model, produces synthetic composite keys, and adds churn with no defect reduction. - **Do not enforce uniformly.** A static, stateless list cannot exhibit the bug. The lists worth the enforcement budget are the ones whose rows hold state, editable fields, focus, scroll or animation — typically editable tables, selectable lists, filtered feeds and anything with per-row inputs. Say so explicitly, or teams will read the policy as bureaucracy and route around it. - **Do not try to standardise the frameworks first.** Identity behaviour differs between them, but the requirement — a stable identity per item — does not. Stating the requirement at the data layer works across all of them; a migration to unify platforms does not need to be on this critical path. ## Making it still true in a year | Mechanism | Survives a year because | Fails when | |---|---|---| | Identity in the payload contract | reviewed at the boundary, benefits every consumer | new endpoints ship without the rule | | Required-identity list helper | the unsafe call cannot be written | teams bypass it for the raw primitive | | Automated check with a ratchet | the count only moves one way | warnings stay warnings and the baseline drifts up | | Templated reorder-state test | it fails loudly on regression | it is optional, or only on new lists | | Named symptom in triage | reports arrive labelled | nobody owns the class | The pattern is the usual one: guarantees that live in a contract or a required argument survive turnover; guarantees that live in a memo and a reviewer's attention do not. ## What to say when asked for the one-sentence policy Every rendered collection identifies its items by a value that belongs to the item, and any list whose rows hold state ships a test that the state follows the item across a reorder. Everything else in the programme exists to make that sentence cheap to comply with.
- What makes a shared list helper actually prevent the bug rather than document it?Making the identity argument required, not optional, so there is no default that silently maps by position. Accept either a field name or a function, fail loudly in development on a missing or duplicated identity, and keep the helper's capabilities close enough to the underlying primitive that teams have no reason to drop back to it.
- Would you mandate globally unique keys to be safe?No. Uniqueness is only needed among siblings, so a global rule enforces something the runtime never checks. It teaches a wrong model, pushes teams into inventing composite keys, and adds review friction with no defect reduction. The rule worth mandating is a stable per-item identity in the data.
- How do you justify the effort when nobody can point at an outage?Frame it by what the bug produces: wrong-record edits and misattributed selections, which are data-integrity incidents, not cosmetic glitches. Then count the triaged-as-flaky reports that match the signature. The cheap end of the programme — an identity field in the contract and a templated test — is small enough not to need a business case.
saying these in an interview costs you the question
- Treats wrong-row reports as unreproducible flakiness
- Relies on code review alone to catch missing identity
- Mandates keys unique across the whole application
- Bans index keys by memo with no enforcement
- Assumes a warning-free build means identity is correct
- Enforces uniformly across static and stateful lists alike