Topics in this subject
React 3 min read Updated 5 Aug 2026

Lists & Keys

Render arrays with .map and stable unique keys; avoid index keys, which cause state and DOM bugs on reorder.

Rendering arrays with .map#

Return an array of elements from .map. Each element needs a key.

function TodoList({ todos }) {
  return (
    <ul>
      {todos.map(todo => (
        <li key={todo.id}>{todo.text}</li>
      ))}
    </ul>
  );
}

Why keys must be stable & unique 🎯#

Keys are React's identity tags for list items during reconciliation. When a list changes, React uses keys to match new elements to previous ones so it can:

  • Reuse the existing DOM node and component state for matched keys.
  • Move nodes instead of recreating them.
  • Add/remove only what actually changed.

Requirements:

Requirement Why
Unique among siblings Duplicate keys → React can't distinguish items; warns and misbehaves
Stable across renders A key that changes each render forces remount, losing state and DOM
Derived from data identity Use a DB id / uuid, not the array index or Math.random()

🟢 key={Math.random()} is an anti-pattern: it changes every render, so React remounts every row — destroying focus, animations, and local state, and killing performance.

The index-as-key pitfall ⚠️#

Using the array index as the key works only if the list is static — never reordered, filtered, inserted into, or deleted from. Otherwise indices stay 0,1,2… while the underlying items shift, so React mis-associates state with the wrong item.

// ❌ index key + reorderable/insertable list → bugs
{items.map((item, i) => <Row key={i} item={item} />)}

// ✅ stable identity
{items.map(item => <Row key={item.id} item={item} />)}

Classic symptom: an uncontrolled <input> inside each row keeps the text of the old position after you insert/delete/reorder, because React reused the DOM node bound to that index.

flowchart TD
  subgraph Before
    K0["key=0 (Apple)"]
    K1["key=1 (Banana)"]
  end
  subgraph After["Insert 'Mango' at top (index keys)"]
    N0["key=0 → now Mango, but React reuses Apple's DOM/state"]
    N1["key=1 → now Apple, reuses Banana's node"]
  end
  K0 -->|"index reused, wrong item"| N0
  K1 -->|"index reused, wrong item"| N1

Keys are for reconciliation, not props ⚠️🎯#

key is a reserved React hint consumed by the reconciler — it is not passed to the component. Reading props.key inside the child yields undefined. If the child needs the value, pass it under a different prop name.

// ❌ id is not readable as props.key inside Row
<Row key={item.id} />

// ✅ pass it separately if needed
<Row key={item.id} id={item.id} />

Bonus: changing a component's key is a deliberate way to force a remount (reset its state) — e.g. <Form key={userId} /> resets the form when switching users.

Interview Q&A#

Q1. Why does React require keys on list items? Keys give each item a stable identity so React can match items across renders during reconciliation, reusing/moving DOM nodes and state instead of destroying and recreating them.

Q2. What's wrong with using the array index as a key? Indices don't track item identity. On reorder, insert, or delete, React associates state/DOM with the wrong item, causing subtle bugs. It's only safe for static, never-changing lists.

Q3. Can a child component read its own key via props? No. key is reserved for the reconciler and is not passed as a prop. Pass the value under a different prop name if the child needs it.

Q4. Why is key={Math.random()} bad? It generates a new key every render, so React can't match items and remounts every row — losing state and focus and hurting performance.

Q5. How can changing a key reset a component? Giving a component a new key makes React treat it as a different element, unmounting the old instance and mounting a fresh one, which resets its state.