“Why does React need a key on list items, and what is wrong with using the array index?” This is one of the most asked React questions at every level. The shallow answer is “to silence the warning” or “for performance”. The real answer is about correctness: keys decide which piece of state belongs to which item, and a bad key moves state to the wrong row.
Interviewers often follow up with a broken demo, such as a list of inputs where deleting the first row leaves the wrong text behind, and ask you to explain exactly why it happens.
Before you start
You should know how to render a list with array.map and how component state works. It helps to know that React compares the previous and next element trees to decide what to update, a process called reconciliation. The rules here are the same in React 18 and 19.
The short answer
When React re-renders a list, it matches each new element to an old one by key. Matching elements keep their state and DOM nodes; unmatched old ones are removed and unmatched new ones are created. A key must therefore identify the item, not its position. With index keys, inserting, removing or reordering items shifts which data sits at each index, so React reuses components with the wrong state. Use a stable, unique id from your data, such as a database id. Index keys are acceptable only for lists that never reorder, filter, or insert, and whose items hold no state.
How it works
Consider rows that each contain an uncontrolled input:
function Row({ label }) {
return <li>{label} <input placeholder="note" /></li>;
}
function List({ items }) {
return <ul>{items.map((item, i) => <Row key={i} label={item.label} />)}</ul>;
}Start with [Apples, Bananas, Cherries] and type “ripe” in the Bananas input. Now delete Apples. React compares:
before: key 0 -> Apples key 1 -> Bananas ("ripe") key 2 -> Cherries
after: key 0 -> Bananas key 1 -> CherriesKey 0 still exists, so React keeps the first <li> and its input (empty) and just changes the label to Bananas. Key 1 keeps the input that contains “ripe” and relabels it Cherries. Key 2 disappears, so the last row is removed. The note now sits next to the wrong fruit. Nothing crashed; the data simply moved.
With key={item.id}, React sees that the Apples key is gone and removes that row, while the Bananas row, with its input and its text, is kept intact.
Step-by-step walkthrough
Step 1: Find the identity in your data
Most data already has one: a database id, a slug, an email, an ISBN. Use it:
{users.map(user => <UserCard key={user.id} user={user} />)}Keys need to be unique only among siblings in the same array, not globally.
Step 2: Generate ids when items are created, not when rendered
For items created on the client, assign an id once at creation time:
function addTask(text) {
setTasks(prev => [...prev, { id: crypto.randomUUID(), text, done: false }]);
}Never write key={crypto.randomUUID()} or key={Math.random()} in JSX. A new key on every render makes React destroy and recreate every row each time, losing focus and state and costing performance.
Step 3: Put the key on the outermost element of each item
The key goes on the element returned directly from map, not on something inside it:
{posts.map(post => (
<Fragment key={post.id}>
<dt>{post.title}</dt>
<dd>{post.summary}</dd>
</Fragment>
))}The short <> syntax cannot take a key, so use Fragment explicitly when you need one.
Step 4: Do not rely on the key inside the component
key is consumed by React and is not passed as a prop. If the child needs the id, pass it separately: <Row key={item.id} id={item.id} />.
Worked scenario
A kanban board lets users drag cards between positions. Each card has a local “expanded” state showing details. After dragging the third card to the top, the expanded panel stayed in third position and now showed details for a different card. Checkbox animations also fired on the wrong cards.
The cards were rendered with key={index}. Reordering moved data between indexes but left state at its index.
{cards.map((card, index) => (
<Card key={index} card={card} />
))}Switching to key={card.id} fixed both symptoms. React now moved the existing Card instances, with their state and DOM nodes, to their new positions. As a bonus, CSS transitions and focus followed the cards correctly.
A related bug appeared in a filtered view: hiding completed cards shifted indexes the same way. Stable ids fixed that too, because filtering is just another way items move position.
Common mistake
- “Keys are a performance optimisation.” They affect performance, but their main job is correctness of state and DOM reuse.
- “Index is fine if I do not have inputs.” Any state counts: an expanded panel, a hover animation, a child component’s fetched data, or focus.
- Random keys. They avoid the warning and break everything else.
- Combining index with content like
key={item.name + i}. It still changes when items shift. - Duplicate keys. React warns, and items with the same key can be merged or dropped unpredictably.
Remember that changing a key on purpose is also how you reset a component’s state; the same mechanism is at work.
Verify the behavior
Build the fruit example twice, once with key={i} and once with key={item.id}, plus a delete button:
const initial = [
{ id: 'a', label: 'Apples' },
{ id: 'b', label: 'Bananas' },
{ id: 'c', label: 'Cherries' },
];
export default function Demo() {
const [items, setItems] = useState(initial);
return (
<>
<button onClick={() => setItems(items.slice(1))}>Remove first</button>
<ul>{items.map((item, i) => <Row key={i} label={item.label} />)}</ul>
</>
);
}Type a note in the Bananas row and click “Remove first”. With index keys, the note appears beside Cherries. Change to key={item.id} and repeat: the note stays with Bananas. React DevTools also shows the component instances moving rather than being relabelled.
Follow-up questions
What happens if I omit the key? React falls back to index behaviour and logs a warning in development.
Do keys have to be strings? Strings or numbers. React converts numbers to strings internally, so 1 and '1' collide.
Is it ever right to use the index? For static lists that never change order or length and whose items have no state, such as a fixed list of footer links, the index is harmless.
How do keys relate to reconciliation more broadly? Without keys, React compares children by position. Keys let it match children by identity, which is what makes moving and reordering cheap and correct. See reconciliation and keys.
Interview exercise
A comments list uses key={index} and each Comment component fetches its author’s avatar in an effect keyed on authorId from props. When a new comment is prepended to the list, every avatar briefly shows the wrong person before correcting. Explain why, and what changes with a proper key.
Answer and reasoning
Prepending shifts every comment down one index. With index keys, React keeps each Comment instance at its position and gives it the previous neighbour’s props. Each instance still holds the avatar it fetched for its old author, so it renders that stale avatar, then its effect sees a new authorId, refetches, and corrects. Every row does this, so the whole list flickers and makes N network requests. With key={comment.id}, React recognises the existing comments, keeps their instances and fetched avatars, and mounts exactly one new Comment at the top. One request, no flicker, and no wrong avatars.
Continue learning
Keep going with the React interview questions and the React MCQs. Related notes: reconciliation and keys, resetting forms with keys and objects are not valid as a React child. Official reference: Rendering Lists on react.dev.