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?
answer
- a key that changes every render
- zero matches in the lookup
- full unmount and mount, every time
- index keys at least pair positionally
- identity, not contents, not randomness
basics
~20 sEvery 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.
solid answer
~50 sA key generated during render is different on every render by construction. When React pairs the new children against the previous ones, the parallel walk fails at the first element and the map of previous children yields no hits at all — every new element is a miss, so a new fiber is mounted for it, and every previous fiber is left over and deleted. The result per re-render is a full unmount and mount of the list: DOM nodes recreated, `useState` and `useRef` values discarded, effects torn down and re-run, focus, text selection, scroll position and CSS transitions lost. Index keys are worse than stable ids but far better than this: positional pairing still reuses almost every fiber, so most updates are patches. The reconciler needs the key to be a function of the item, not of the render.
go deeper
Recall that a key must be the same value for the same item on every render, and that generating one inside render breaks that. Uniqueness alone is not enough — stability is the point.
Trace the pairing: the parallel walk fails immediately, the key map yields no hits, every new element mounts and every previous fiber is deleted. Then name what a full remount discards.
Compare the options by how much identity they carry — stable id, index, random — and recognise the disguised versions in a real codebase, such as keys built from the item's contents or from a timestamp.
Own where item identity comes from in the system: ids minted client-side at creation time versus server-assigned ids, and how that choice ripples into optimistic updates, list state and reconciliation cost.
## What the reconciler sees The key is React's only handle on "which element is which". Generating it during render means the answer to that question is deliberately randomised every time. Trace the pairing: 1. The parallel walk compares the first new key with the first previous key. They differ (a fresh UUID never equals an old one), so the fast path stops at index 0. 2. React builds the map from *all* the previous children. 3. Every remaining new element is looked up and misses, so each one gets a brand-new fiber. 4. Nothing was ever claimed from the map, so every previous fiber is deleted. That is the worst possible outcome of the algorithm, reached on every single re-render — even one where the underlying data did not change at all. ## What it costs A delete-and-mount of the whole list, per render: - every host DOM node is destroyed and recreated, so uncontrolled input values, focus, text selection, scroll position inside rows and running CSS transitions are all lost; - every row's `useState`, `useReducer` and `useRef` values are discarded and re-initialised; - every effect in every row is cleaned up and re-run as a mount — including any data fetching, subscription or observer set up there; - refs are detached and re-attached, so anything imperative bound to those nodes is rebuilt. The symptom people report is a list that "flickers" or loses what the user typed whenever anything on the page updates. The mechanism is that nothing was ever reused. ## Why it is worse than an index key Index keys have real problems on insert, reorder and delete, but they are *stable across a render where nothing moved*. If the list did not change, index keys make the parallel walk succeed for every element and the entire reconciliation is a cheap in-place update. Random keys give up that path unconditionally: a re-render caused by something entirely unrelated still rebuilds the list. Ranked by how much identity information the reconciler gets: a stable id derived from the item is best; an index is a weak identity that is correct only while positions do not change; a fresh random value is *anti*-information, guaranteed wrong every time. ## The same bug in other clothing The pattern is not always as obvious as `Math.random()` or `crypto.randomUUID()`: ```jsx // all of these change on every render <Row key={Math.random()} /> <Row key={crypto.randomUUID()} /> <Row key={`${item.name}-${Date.now()}`} /> <Row key={JSON.stringify(item)} /> // changes whenever any field changes ``` The stringified-object key is the subtle one: it is deterministic, but it is a function of the item's *contents* rather than its identity, so editing one field of a row remounts that row and throws away its local state. A key must answer "which thing is this", not "what does this thing currently contain". ## Where a generated id is legitimate Generating an identifier is fine — generating it *during render* is not. If a client-created row genuinely has no server id yet, create the id once when the row is created (in the event handler that appends it) and store it on the item in state. From then on the key is read from the data and is stable for the row's lifetime, which is exactly what the reconciler needs. ## Answering well Describe the pairing failing completely — zero hits in the map, every previous fiber deleted — then list what a full remount costs, then make the comparison: index keys still let the parallel walk succeed when nothing moved, so they reuse fibers that random keys never do.
- Is key={JSON.stringify(item)} an acceptable alternative?No. It is deterministic but it keys on contents rather than identity, so editing any field produces a new key and remounts that row — discarding its local state, focus and uncontrolled input values. Keys must answer "which item is this", which means deriving them from a stable identifier, not from the item's current values.
- Client-created rows have no server id yet. Where should their id come from?Generate it once, in the handler that creates the row, and store it on the item in state. The key is then read from the data and stays stable for that row's lifetime. Generating it inside render is what breaks the reconciler, not generating it at all.
- Would wrapping the rows in React.memo rescue a list with random keys?No. Memoisation only skips re-rendering a fiber that is being reused, and here no fiber is reused — every previous one is deleted and every new one is mounted. The memo comparison is never even reached for a freshly mounted component.
saying these in an interview costs you the question
- Random keys are fine because they are guaranteed unique
- Unique keys are all React needs; stability does not matter
- React falls back to matching by position when no key matches
- Stringifying the item makes a good key
- Memoizing the row component avoids the remount