What Is the Purpose of the key Prop in React Lists?
Advertisement
🧩 Scenario
Architecture Walkthrough
What key Does During Reconciliation
When React re-renders a list, it compares the previous list of elements to the new one. Without keys, it matches by position: the first element in the old list maps to the first element in the new list, the second to the second, and so on. This works correctly only when the list does not change order and items are only appended at the end.
With keys, React can match elements by identity across positions. If item with key="abc" moves from position 2 to position 0, React recognizes it as the same item and moves the DOM node instead of destroying and recreating it. Only truly new or removed items result in DOM mutations.
Why Index as Key Is Dangerous
When items can be sorted, filtered, prepended, or reordered, the index-to-item mapping changes. After sorting, the item that was at index 2 is now at index 0. React sees key 0 in the new list and assumes it is the same element as the old key 0, even though they are different items. It tries to reuse the DOM node and component state from the wrong item.
This causes uncontrolled input values to appear on the wrong item, checkboxes to stay checked for the wrong entry, animations to fire on the wrong element, and other state bugs that are difficult to reproduce and trace.
key as a Reset Mechanism
Because React remounts a component when its key changes, you can use this deliberately. Passing a different key to a component forces it to unmount and remount as a completely fresh instance, resetting all local state and effects. This is the idiomatic way to reset a form when the selected record changes, rather than using useEffect to manually reset each state variable.
Key Code Explained
// BAD: index as key — causes bugs when list can reorder or filter
function TodoListBad({ todos }: { todos: Todo[] }) {
return (
<ul>
{todos.map((todo, index) => (
// If todos.filter() removes item at index 1, item at index 2
// becomes index 1 — React reuses the wrong DOM node
<li key={index}>
<input type="checkbox" defaultChecked={todo.done} />
{todo.text}
</li>
))}
</ul>
);
}
// GOOD: stable ID from data as key
interface Todo {
id: string;
text: string;
done: boolean;
}
function TodoList({ todos }: { todos: Todo[] }) {
return (
<ul>
{todos.map((todo) => (
// React uses todo.id to match elements across renders
// Sorting or filtering the list does not confuse React
<li key={todo.id}>
<input type="checkbox" defaultChecked={todo.done} />
{todo.text}
</li>
))}
</ul>
);
}
// Sortable table: why stable keys matter
function EmployeeTable({ employees }: { employees: Employee[] }) {
const [sortBy, setSortBy] = useState<'name' | 'salary'>('name');
const sorted = [...employees].sort((a, b) =>
sortBy === 'salary' ? b.salary - a.salary : a.name.localeCompare(b.name),
);
return (
<table>
<tbody>
{sorted.map((emp) => (
// With emp.id as key, React knows the row for "Alice" moved
// and updates only the position, not the entire cell content
<tr key={emp.id}>
<td>{emp.name}</td>
<td>{emp.salary}</td>
</tr>
))}
</tbody>
</table>
);
}
// key as a deliberate remount/reset trigger
interface UserFormProps {
userId: string;
}
function UserForm({ userId }: UserFormProps) {
const [name, setName] = useState('');
const [email, setEmail] = useState('');
// ...form logic
return (
<form>
<input value={name} onChange={(e) => setName(e.target.value)} />
<input value={email} onChange={(e) => setEmail(e.target.value)} />
</form>
);
}
function UserEditor({ selectedUserId }: { selectedUserId: string }) {
return (
// When selectedUserId changes, React sees a new key and fully remounts UserForm
// All local state in UserForm resets automatically — no useEffect needed
<UserForm key={selectedUserId} userId={selectedUserId} />
);
}
// Common mistake: Math.random() as key (DO NOT DO THIS)
// This generates a new key every render, forcing every item to remount every time
{todos.map((todo) => (
<TodoItem key={Math.random()} todo={todo} /> // every render = full remount = bug
))}
The UserEditor pattern using key={selectedUserId} is the correct way to reset a form when the selected record changes. Without this, you would need useEffect(() => { setName(''); setEmail(''); }, [selectedUserId]) for every state variable, and you would still risk stale state flashing on screen between the user change and the effect running.
Tradeoffs
| Key choice | Safe for static lists | Safe for reorderable lists | Notes |
|---|---|---|---|
| Stable ID from data | Yes | Yes | Always prefer this |
| Array index | Yes (append-only) | No | Causes state bugs on sort/filter/reorder |
| Math.random() | No | No | New key every render forces full remount |
| Composite key (a+b) | Yes | Yes (if combination unique) | Use when no single unique ID exists |
What Interviewers Actually Check
- Whether you can explain what key does during reconciliation
- Whether you know why index as key is dangerous for reorderable lists
- Whether you know that key is not accessible as a prop inside the child
- Whether you know the deliberate remount pattern (
key={selectedId}) to reset state - Whether you know
Math.random()as a key is always wrong
Follow-Up Questions
- React requires keys for direct children of a
Fragmenttoo. When does this come up and why? - If you have a list of items with no unique ID from the API, what key strategies would you consider?
- How does the key prop interact with
React.memo— does a key change trigger the memo comparison or bypass it? - Can two different lists on the same page use the same key values? Why does it work?
- How does React's key mechanism differ between client-side rendering and React Server Components?
Common Candidate Mistakes
- Using array index as a key for a sortable or filterable list and not understanding why checkboxes or input values appear on the wrong items
- Using
Math.random()as a key thinking it is "always unique," not knowing it causes every item to remount on every render - Putting the
keyon an inner element inside themap()callback instead of on the outermost element returned - Not knowing that
props.keyis undefined inside the child — key is reserved by React and is not forwarded as a prop - Thinking keys must be globally unique across the entire app, when they only need to be unique among siblings in the same list
Interview Readiness Checklist
Before you leave this question, make sure you can answer:
- Can you explain what key does during reconciliation and why it matters for list performance?
- Can you explain why index as key causes bugs when the list can sort, filter, or reorder?
- Can you use a stable, unique ID from the data source as the key?
- Can you use
keyas a deliberate reset mechanism to force a component to remount? - Can you explain that key is reserved by React and is not accessible as
props.keyinside the child?
Summary
The key prop tells React how to match list elements across renders. Without keys, React matches by position, which breaks whenever items change order, are inserted in the middle, or are removed. With stable unique keys, React identifies each element by its key and can move, update, or remove only the elements that actually changed.
Using array index as a key is a common mistake. It only works correctly for append-only lists where order never changes. For any list that can be sorted, filtered, or reordered, index as key causes React to reuse the wrong DOM node and component state, producing bugs with uncontrolled inputs, checkboxes, animations, and focus that are difficult to trace.
A useful secondary feature of key is forced remounting. When a component's key changes, React unmounts and remounts it as a completely fresh instance. This is the idiomatic way to reset a form or child component when the selected record changes, without writing useEffect to manually reset each state variable. Keys must be unique among siblings in the same list; they do not need to be globally unique.
Why does React warn about missing keys in lists?
Without keys, React falls back to positional matching during reconciliation. Adding, removing, or reordering items causes React to update the wrong DOM nodes, leading to incorrect state, focus, and animation bugs.
Advertisement
Stay Updated
Get the latest frontend challenges, interview questions and tutorials delivered to your inbox.
Advertisement