Why array indices make bad React keys
TL;DR
Using array indices as key props looks fine until the list is reordered, filtered, or has items removed. React uses keys to figure out which component corresponds to which item between renders — with indices, that mapping breaks the moment positions change, and you get stale state, wrong content, and visual glitches.
What keys are actually for
When React renders a list, it needs to match items between renders so it can update the DOM efficiently instead of rebuilding it from scratch.
// React uses keys to match items between renders{items.map((item) => (<ListItem key={item.id} item={item} />));}
Between renders, React uses keys to decide:
- Which items are new and need to be mounted
- Which items are gone and need to be unmounted
- Which items moved and need to be reordered
Where index keys fall apart
Consider:
{items.map((item, index) => (<ListItem key={index} item={item} />));}
Start with ["Apple", "Banana", "Cherry"] and delete "Apple":
Before deletion:
Index (key)Item0Apple1Banana2Cherry
After deletion:
Index (key)Item0Banana1Cherry
From React's point of view, key 0 still exists — but its props changed from "Apple" to "Banana". So React mutates the existing <ListItem> in place rather than mounting a fresh one. It then unmounts key 2 (the old "Cherry" instance), even though conceptually "Cherry" is still in the list. Everything lines up wrong.
The bug you actually ship
The issue becomes obvious the moment list items carry their own local state:
function TodoList() {const [todos, setTodos] = useState(['Buy milk','Walk dog','Read book',]);const removeTodo = (index) => {setTodos(todos.filter((_, i) => i !== index));};return (<ul>{todos.map((todo, index) => (<TodoItemkey={index}text={todo}onRemove={() => removeTodo(index)}/>))}</ul>);}function TodoItem({ text, onRemove }) {const [checked, setChecked] = useState(false);return (<li><inputtype="checkbox"checked={checked}onChange={() => setChecked(!checked)}/>{text}<button onClick={onRemove}>Remove</button></li>);}
Check "Buy milk", then delete it. The checked state lives in the TodoItem at key 0. After deletion, key 0 is "Walk dog" — and it shows up checked, even though you never touched its box.
When indices are actually fine
You can get away with index keys when all three of these hold:
- The list never reorders, adds, or removes items.
- Items carry no local state (no form inputs, no checkboxes, no animations).
- There's no stable unique id on the data.
Example — a static nav:
{navLinks.map((link, index) => (<a key={index} href={link.url}>{link.label}</a>));}
What to use instead
Anything stable and unique per item:
// Use an ID from the data{users.map((user) => (<UserCard key={user.id} user={user} />));}// If the data has no ID, generate one when creating the itemconst newTodo = {id: crypto.randomUUID(),text: 'New todo',};
The key here is "generate once, when the item is created" — not per render.
Common Pitfalls
Never use Math.random() or Date.now() inline as a key. Those values change every render, so React treats every item as new every time, unmounting and remounting everything. That's strictly worse than index keys.
Interview Tip
The checkbox example is the most visceral way to demonstrate the bug — a check mark "jumps" to the wrong item. Then explain the fix (stable id) and, as a bonus, list the three conditions where index keys are legitimately OK. That trio shows you understand the rule rather than parroting it.
Why interviewers ask this
This question separates people who know the rule from people who understand reconciliation. A lot of candidates can recite "don't use index as key" without being able to say why or when it's fine. A senior-level answer covers the mechanism (how React matches keyed elements) and the exceptions.