Topics in this subject
React 6 min read Updated 6 Aug 2026

Lists & Keys

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

🧑‍🏫 Sabse pehle — simple mein samjho#

Jab tumhare paas ek array ho (jaise todos ki list), tum use .map se UI list mein badalte ho. Har item ko ek key deni padti hai — yeh us item ka unique roll number hai, taaki add/remove/reorder pe React ko pata rahe kaun-sa item asal mein kaun hai. Jaise class mein har bachche ka roll number fix hota hai, naam badlein bhi to roll number se pehchaan hoti hai. Isiliye index ko key mat banao jab list badalti rehti ho.

function Fruits() {
  const fruits = [{ id: 1, name: "Aam" }, { id: 2, name: "Kela" }];
  // key = har item ka unique id (roll number), index nahi
  return <ul>{fruits.map(f => <li key={f.id}>{f.name}</li>)}</ul>;
}

Yaad rakho: har list item ko ek stable unique key do — id use karo, index nahi.

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

Bug repro: deleting a middle item from a Zomato/Swiggy order-items list ⚠️#

Say a checkout screen lists order items, each with an uncontrolled <input> for a per-item cooking note ("less spicy", "extra butter"):

// ❌ index as key — each row also carries local input state
function OrderItems({ items, onRemove }) {
  return (
    <ul>
      {items.map((item, i) => (
        <li key={i}>
          <span>{item.name}</span>
          <input placeholder="Add a note (e.g. less spicy)" />
          <button onClick={() => onRemove(item.id)}>Remove</button>
        </li>
      ))}
    </ul>
  );
}

Order: [Paneer Tikka, Butter Naan, Gulab Jamun] at indices 0, 1, 2. The user types "extra butter" into Butter Naan's note (index 1), then taps Remove on Paneer Tikka (index 0). The array shifts to [Butter Naan, Gulab Jamun] at indices 0, 1. Because the key is the index, React thinks key=0 and key=1 are the same rows as before — it reuses the existing <li> DOM nodes (and their <input> values) as-is and only patches the text content:

  • key=0 keeps the DOM node that used to be Paneer Tikka (empty note) — now labeled Butter Naan, note is empty ⚠️.
  • key=1 keeps the DOM node that used to be Butter Naan (with "extra butter" typed) — now labeled Gulab Jamun, which inherits the wrong typed note ⚠️.

The note the user typed appears to "jump" onto a completely different dish. Fixing it is one prop:

// ✅ stable id as key — each DOM node (and its input state) travels with its own data
function OrderItems({ items, onRemove }) {
  return (
    <ul>
      {items.map(item => (
        <li key={item.id}>
          <span>{item.name}</span>
          <input placeholder="Add a note (e.g. less spicy)" />
          <button onClick={() => onRemove(item.id)}>Remove</button>
        </li>
      ))}
    </ul>
  );
}

With key={item.id}, removing Paneer Tikka simply removes its <li>; Butter Naan's own DOM node (note and all) is untouched and moves up visually with no content patch needed.

flowchart TD
  subgraph IdxBefore["Before delete — key = index"]
    I0["key=0 → Paneer Tikka, note=''"]
    I1["key=1 → Butter Naan, note='extra butter'"]
    I2["key=2 → Gulab Jamun, note=''"]
  end
  subgraph IdxAfter["After removing Paneer Tikka — key = index"]
    J0["key=0 → now Butter Naan, reuses Paneer Tikka's node: note='' ⚠️"]
    J1["key=1 → now Gulab Jamun, reuses Butter Naan's node: note='extra butter' ⚠️"]
  end
  I1 -->|"DOM node kept, wrong data lands on it"| J0
  I2 -->|"DOM node kept, wrong data lands on it"| J1

  subgraph IdBefore["Before delete — key = item.id"]
    D1["key=bnaan → Butter Naan, note='extra butter'"]
    D2["key=gjamun → Gulab Jamun, note=''"]
  end
  subgraph IdAfter["After removing Paneer Tikka — key = item.id"]
    E1["key=bnaan → Butter Naan, note='extra butter' 🟢 unchanged"]
    E2["key=gjamun → Gulab Jamun, note='' 🟢 unchanged"]
  end
  D1 -->|"same key, node just moves"| E1
  D2 -->|"same key, untouched"| E2

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.