Question and Applicable Scenario
An admin page renders an editable user list. Each Row owns an unsubmitted input draft and expanded state. Users can insert a record at the top, delete a middle record, sort, and filter. A detail form beside the list should clear the previous user's local state whenever userId changes.
Explain:
- How React decides whether a component is the same component between adjacent renders.
- Why
key={index}can move drafts or expanded state to another record. - Why
key={Math.random()}repeatedly remounts components. - When a stable domain ID should preserve state and when changing a key should reset a subtree.
- How to verify the behavior across insertion, deletion, sorting, filtering, and entity switches.
React interview material published in March and May 2026 directly includes keys, index keys, reconciliation, and remounting. Although the prompt looks like list syntax, it tests whether a candidate can model which logical entity owns state and express that model through component boundaries and tests.
This is a frontend question because its core competency is React component identity, state lifetime, and DOM reuse.
What the Interviewer Is Evaluating
First, the candidate should know that state is not automatically attached to a JSX tag or domain object. React associates state with a position in the render tree. Within one parent, element type and key help determine whether old and new children represent the same identity.
Second, the candidate must distinguish a re-render from a remount. New props or a parent update can re-render the same identity while preserving its local state. A changed component type or key creates a new identity, destroys the old subtree's local state, and may recreate its DOM.
Third, the candidate should derive keys from domain identity. A database ID or a UUID generated and stored when a record is created usually means "this is still the same record." An array position, current timestamp, or random value produced during render does not.
Fourth, a strong answer does not turn "never change a key" into a rule. When a detail form switches from user A to user B, the forms may represent different domain entities. key={userId} at the correct boundary can reset the complete form subtree more reliably than clearing individual state variables in Effects.
Finally, the candidate should name the cost of remounting: focus, scroll position, unsubmitted input, and descendant state can all be lost. If the product must restore a draft when an entity is selected again, lift the draft, store it by entity, or persist it externally instead of depending on local state in a removed subtree.
Clarifying Questions Before Answering
- Can the list insert, delete, sort, or filter items? Once membership or order can change, an array index does not stably
identify a domain entity.
- Does each row own React or browser DOM state? Inputs, expansion state, animations, and uncontrolled fields expose incorrect
reuse quickly.
- Does the data have a stable ID unique among its siblings? Keys need sibling uniqueness, not global uniqueness.
- Should an entity switch discard or restore its draft? Discarding fits a key change; restoration requires a longer-lived
state layer.
- Should the entire subtree reset or only one field? Use a key for a complete identity reset; prefer controlled state or an
explicit data update for a partial adjustment.
- When is the ID generated? A local record may receive a UUID when it is created and keep it. Do not generate a new one on
every render.
30-Second Answer Framework
"React associates state with positions in the render tree. Under the same parent, component type and key help React determine whether old and new nodes have the same identity. A stable key lets row state follow a domain record even when it moves. With an array index, insertion or sorting can put a different record in the same slot, so a local draft can move to the wrong row. A random key never matches the previous render, so React repeatedly recreates the component and DOM.
Lists should use a stable ID from the data. If switching from user A's detail form to user B must clear all local state, I would put key={userId} on the form-subtree boundary to state that this is a new entity. If each user's draft must survive, I would lift drafts and store them by userId while keeping the identity boundary. I would verify insertion, deletion, reordering, filtering, same-ID re-renders, and different-ID switches to prove that state follows the intended entity."
Step-by-Step Deep Dive
Step 1: Build a component identity model.
A useful interview model is:
| Relationship between old and new render | Typical result |
|---|---|
| Same parent, same component type, same key | Preserve that identity's local state; the component may re-render |
| Same parent, same type, different key | Remove the old identity, mount a new one, and reset subtree state |
| Same position, different component type | Replace the old subtree and reset state |
| List without an explicit key | Fall back to positional matching, which is unsafe for dynamic lists |
key is not an ordinary prop delivered to the component. It is a React hint. If Row also needs the domain ID, pass a separate prop such as rowId={item.id}.
A key also defines identity only within its current parent. Two separate lists may both contain key="user-42", while siblings inside one list must not have duplicate keys.
Step 2: Reproduce the index-key bug with an editable list.
Incorrect implementation:
function UserList({ users }: { users: User[] }) {
return users.map((user, index) => (
<EditableRow key={index} user={user} />
))
}Assume the initial order is [Alice, Bob]. The EditableRow at index 0 stores Alice's draft. Insert Zoe at the top, producing [Zoe, Alice, Bob]. React can match the old row with key 0 to the new item at index 0, so state that belonged to Alice may appear in Zoe's row. State at key 1 can similarly move from Bob to Alice.
The problem is not that an index is a number. The index identifies a slot while the product needs to identify a user. Deletion, sorting, and filtering all change the mapping between slots and entities.
Correct implementation:
function UserList({ users }: { users: User[] }) {
return users.map((user) => (
<EditableRow key={user.id} rowId={user.id} user={user} />
))
}After Zoe is inserted, Alice still has Alice's ID. She can move from index 0 to index 1, and React can continue matching Alice's component state to Alice.
If one record returns several sibling nodes, the short Fragment syntax cannot take a key. Use an explicit Fragment:
import { Fragment } from 'react'
users.map((user) => (
<Fragment key={user.id}>
<UserHeading user={user} />
<EditableRow user={user} />
</Fragment>
))Step 3: Choose a stable key instead of manufacturing one during render.
A practical preference order is:
- A stable record ID from the backend or database.
- A unique identifier already attached to the data and unchanged throughout its domain lifetime.
- For a local-only new record, a UUID generated in the create-record event and persisted with the record.
- A composite key only when its fields truly form an immutable, sibling-unique domain identity.
The following creates a new identity on every render:
<EditableRow key={Math.random()} user={user} />This is not an ordinary re-render. React removes the old row and mounts a new one. Local state and user input are lost, and the DOM is recreated. Date.now() or calling crypto.randomUUID() during render has the same problem. A UUID is fine when generated once during item creation and stored, not when generated while rendering the item.
An array index is acceptable only under a narrow contract: membership and order remain fixed for the list's entire lifetime, there is no insertion, deletion, filtering, or reordering, position itself is the identity, and no state must follow a domain entity. Dynamic business data should receive a real ID rather than rely on those assumptions.
Step 4: Use a key to express "this is a different form."
A common attempted fix on detail pages is:
function Profile({ userId }: { userId: string }) {
const [comment, setComment] = useState('')
useEffect(() => {
setComment('')
}, [userId])
return <CommentForm value={comment} onChange={setComment} />
}This first renders the tree with the stale comment and then triggers another render after the Effect runs. More importantly, a deep detail page can also contain attachments, validation errors, and nested form state. Clearing one comment variable does not guarantee that the entire subtree resets.
If the product defines each user's form as a distinct entity, place the key at the identity boundary:
function ProfilePage({ userId }: { userId: string }) {
return <ProfileForm key={userId} userId={userId} />
}When userId changes, React treats the new ProfileForm as a different identity and resets its local state and all descendant state. When unrelated parent state causes a re-render with the same userId, the key remains stable and local state is preserved.
Place the key on the smallest complete boundary that must reset. Keying the whole page also recreates navigation, expensive displays, and unrelated scroll state. Keying one input may leave other state from the same form behind.
Step 5: Decide what the product must preserve before choosing a remount.
"Switch entity" does not automatically mean "discard draft." Use a decision table:
| Product semantics | Recommended state design |
|---|---|
| Another entity must receive a completely fresh form | Use the entity ID as the form-subtree key |
| Returning to an entity must restore its draft | Keep identity keys; lift drafts and store them by ID |
| A page refresh must also restore drafts | Persist them externally with expiration and cleanup rules |
| Reset one field while preserving the rest | Use controlled state or an explicit update; do not remount the subtree |
| Props only change a derived display value | Calculate from props during render instead of copying into state |
Keys define identity boundaries; they do not provide long-term persistence. Once state is lifted into drafts[userId], a form subtree can unmount while its draft remains in the parent. Selecting that user again can initialize or control the form with the stored value.
Step 6: Recognize other causes of accidental resets.
Even with correct keys, changing a component type resets state. Switching the same tree position from ProfileForm to LoginPrompt replaces the subtree.
Another common mistake is defining a component inside another component:
function ProfilePage() {
function ProfileForm() {
const [name, setName] = useState('')
return <input value={name} onChange={(event) => setName(event.target.value)} />
}
return <ProfileForm />
}Every ProfilePage render creates a new ProfileForm function object. React sees a different component type and unexpectedly resets the input state. Component definitions should remain at the top level. An answer focused only on keys can miss this related identity bug.
Step 7: Apply the same identity rule to virtualized lists.
A virtualized list repeatedly reuses a small number of visible slots. If the library accepts an itemKey, return the domain entity ID rather than the window index. Otherwise, scrolling or reordering can let one entity inherit state from another slot.
For large lists, an even safer design often reduces non-persistent business state inside rows. Store edit drafts above the list by record ID and let each row read its entity's draft. Then a virtualization library can unmount off-screen rows without deleting the business data.
Step 8: Verify state ownership, not just rendered text.
For every test, state which ID should own the state:
| Operation | Expected result |
|---|---|
| Type a draft in Alice, then insert Zoe at the top | The draft still belongs only to Alice |
| Delete a middle item | Other rows' expanded and input state does not migrate |
| Sort descending, then restore the original order | Each row's state continues to follow its record ID |
| Filter Alice out, then restore the filter | Row-local state is lost after unmount; restore from a lifted draft store if required |
| Trigger a parent re-render with the same userId | The form's local state is preserved |
| Switch from user A to user B | A form keyed by userId resets completely |
| Temporarily use a random key and trigger a parent re-render | Input loss and DOM recreation demonstrate the failure mechanism |
| Receive duplicate IDs | Warn or reject at the data boundary to avoid sibling key conflicts |
Tests should also cover focus and uncontrolled inputs. A wrong key may leave React state looking reasonable while browser-held DOM values or focus move to the wrong record. Acceptance criteria should describe entity continuity visible to users, not just render counts.
High-Quality Sample Answer
"I treat a key as part of component identity, not as an attribute that merely removes a console warning. React associates state with render-tree positions. Under one parent, the same component type and key usually represent the same identity, so prop updates or movement can re-render while preserving state. A changed type or key represents a new identity: React removes the old subtree and mounts a new one.
In an editable list, the index identifies a position, not a user. If [Alice, Bob] uses keys 0 and 1 and Zoe is inserted at the top, the old row with key 0 can now render Zoe, so Alice's draft or expanded state may appear under Zoe. I would use user.id, which keeps Alice's identity stable when she moves from index 0 to index 1. A random key, timestamp, or UUID generated during render never matches the previous render, so React repeatedly recreates components and DOM and loses input. Keys only need to be unique among siblings, and the child does not receive key as a prop.
The detail form depends on product semantics. If switching from user A to user B must clear the complete form, I would put key={userId} on the ProfileForm boundary so all descendant state resets together instead of clearing fields in several Effects. If returning to A must restore a draft, I would keep identity separated by userId but lift or externally persist drafts by userId.
Finally, I would test insertion at the top, deletion, reordering, filtering, same-ID re-renders, and different-ID switches. I would check that input, expansion state, focus, and drafts all follow the intended domain ID. That proves the key models the correct identity rather than merely allowing the page to update."
Common Mistakes
- Explaining keys only as a performance optimization → The core issue is child identity and state ownership →
Explain state mismatch first, then update cost.
- Using array indices for every list → Insertion, deletion, reordering, and filtering change the slot-to-entity mapping →
Use a stable ID from the data.
- Generating a UUID or random value during render → The key changes every time and recreates components and DOM →
Generate and store the ID when the item is created.
- Requiring keys to be globally unique → React requires uniqueness among siblings →
Evaluate conflicts within the current parent and list.
- Reading
props.keyin the child → React does not pass key as an ordinary prop →
Pass a separate rowId or userId.
- Clearing a complex form field by field in Effects → This renders stale state, adds another render, and can miss descendant
state → Change the key at the correct identity boundary.
- Remounting the whole page to clear one field → This loses focus, scroll, and unrelated state →
Narrow the key boundary or update the field explicitly.
- Assuming a stable key preserves state after filtering → A filtered component may have unmounted →
Lift or persist state when restoration is required.
Follow-Up Questions
Follow-up 1: What key should be used when data has no backend ID?
Generate an ID, such as a UUID, in the event that creates the local record and store it as a record field. Every later render reads the same ID. An immutable, sibling-unique combination of domain fields can work if that contract is proven. Do not generate the key temporarily inside the map render.
Follow-up 2: When is an array index acceptable as a key?
It is relatively safe only when membership and order stay fixed for the list's entire lifetime, there is no insertion, deletion, filtering, or reordering, and position itself is the identity. A fixed static display may satisfy that boundary. Dynamic business data should use stable entity IDs because sorting or editing immediately invalidates the index assumptions.
Follow-up 3: If only one field should clear when userId changes, should the whole form key change?
Not necessarily. A key resets the complete subtree. If other local state must remain, use a controlled field, derive a value directly from props, or adjust it in an explicit data update path. Choose a key reset only when the whole subtree represents another entity and all of its local state should start over.
Follow-up 4: How can a draft be restored after changing the key?
Lift drafts to a parent, for example as drafts[userId], or persist them externally with expiration and cleanup rules. Keep the form key based on userId so different users do not share local state. A newly mounted form reads its initial value from the corresponding stored draft. Identity isolation and draft persistence are separate responsibilities.
Follow-up 5: Does a virtualized list still need stable keys when it already reuses DOM?
Yes. Virtualization controls the number of visible nodes; it does not redefine domain identity. If the library exposes itemKey, return the record ID. Editing state that must survive unmounting should also live above the rows by ID, or it will still disappear when a row scrolls out of the window.