ReactPerformanceMedium

Why are array indices bad keys in React?

01

The Short Answer

Using array indices as keys in React causes bugs when the list order changes — items get reordered, inserted, or deleted. React uses keys to match elements between renders. When you use indices, React thinks element at position 0 is always the same item, even if the actual data shifted. This leads to incorrect DOM reuse, broken component state, stale inputs, and wasted re-renders. Always use a stable, unique identifier from your data instead.

02

How React Uses Keys

When React re-renders a list, it needs to figure out which items are new, which were removed, and which just moved. Keys are the mechanism for this — React matches old and new elements by their key. If a key exists in both the old and new render, React reuses the existing DOM node and component instance (preserving state). If a key is new, React creates a fresh instance.

  • React compares old keys with new keys
  • Same key → reuse DOM node and component state
  • New key → create new DOM node and component instance
  • Missing key → destroy the old DOM node and unmount
03

What Goes Wrong with Index Keys

Consider a todo list where you add an item to the beginning. With index keys, every item's key shifts — what was key=0 is now key=1, what was key=1 is now key=2. React sees key=0 in both renders and assumes it's the same element, so it reuses the old DOM node and state. But the data at that position changed — so React updates the text but keeps the old component state (like checkbox checked status).

index-key-bug.tsxtypescript
function TodoList() {
  const [todos, setTodos] = useState([
    { id: 'a', text: 'Buy milk' },
    { id: 'b', text: 'Walk dog' },
  ]);

  function addToTop() {
    setTodos([{ id: 'c', text: 'New task' }, ...todos]);
  }

  return (
    <ul>
      {/* ❌ Index keys — breaks when list order changes */}
      {todos.map((todo, index) => (
        <li key={index}>
          <input type="checkbox" /> {/* State gets mismatched! */}
          {todo.text}
        </li>
      ))}
    </ul>
  );
}

// Before addToTop:
//   key=0 → "Buy milk" (checkbox: checked ✓)
//   key=1 → "Walk dog" (checkbox: unchecked)
//
// After addToTop:
//   key=0 → "New task" (gets "Buy milk"'s checked state! 💥)
//   key=1 → "Buy milk" (gets "Walk dog"'s unchecked state! 💥)
//   key=2 → "Walk dog" (new instance, fresh state)

The checkbox state is tied to the key, not the data. When you prepend an item, all indices shift by one — React reuses the wrong DOM nodes and the checked state ends up on the wrong todo. This is the classic index-key bug.

04

The Correct Approach

Use a stable, unique identifier that stays with the item regardless of its position in the array. Database IDs, UUIDs, or any value that uniquely identifies the data item works. The key should be derived from the data itself, not its position.

stable-keys.tsxtypescript
function TodoList() {
  const [todos, setTodos] = useState([
    { id: 'a', text: 'Buy milk' },
    { id: 'b', text: 'Walk dog' },
  ]);

  function addToTop() {
    setTodos([{ id: crypto.randomUUID(), text: 'New task' }, ...todos]);
  }

  return (
    <ul>
      {/* ✅ Stable keys — correct behavior regardless of order */}
      {todos.map((todo) => (
        <li key={todo.id}>
          <input type="checkbox" />
          {todo.text}
        </li>
      ))}
    </ul>
  );
}

// Before addToTop:
//   key="a" → "Buy milk" (checkbox: checked ✓)
//   key="b" → "Walk dog" (checkbox: unchecked)
//
// After addToTop:
//   key="c" → "New task" (new instance, fresh state) ✅
//   key="a" → "Buy milk" (same instance, keeps checked state) ✅
//   key="b" → "Walk dog" (same instance, keeps unchecked state) ✅

Now React correctly identifies each item across renders. The new item gets a fresh DOM node, and existing items keep their state intact regardless of where they appear in the array.

05

Performance Impact

Beyond correctness bugs, index keys also hurt performance. When you insert an item at the beginning of a list with index keys, React thinks every single item changed (because every index shifted). It re-renders every list item instead of just inserting one new node. With stable keys, React knows the existing items didn't change and only creates the one new element.

OperationIndex keys (work done)Stable keys (work done)
Append to end1 new node (fine)1 new node (fine)
Prepend to startRe-render ALL items1 new node + reorder
Remove from middleRe-render items after removal1 node removed
Reorder/sortRe-render ALL itemsMove existing nodes (no re-render)
Insert in middleRe-render items after insertion1 new node + reorder
06

When Index Keys Are Acceptable

There are narrow cases where index keys won't cause problems — but they're rare enough that defaulting to stable keys is always safer. Index keys are only safe when all three conditions are true simultaneously.

Index keys are safe ONLY when ALL of these are true

  • The list is static — items are never reordered, inserted, or deleted
  • Items have no component state (no inputs, checkboxes, or internal state)
  • Items have no stable unique ID available

In practice, this means index keys are only safe for purely static, display-only lists that will never change. The moment you add sorting, filtering, pagination, or any interactivity — you need stable keys.

07

Why Interviewers Ask This

This question tests your understanding of React's reconciliation algorithm and how the virtual DOM diffing works. Interviewers want to see that you can explain the specific bugs index keys cause (state mismatch, incorrect DOM reuse), understand the performance implications, know what makes a good key (stable, unique, derived from data), and can identify the rare cases where index keys are acceptable. It's a practical question — this bug shows up in production code constantly.

Quick Revision Cheat Sheet

Why keys exist: React matches old/new elements by key to reuse DOM nodes and state

Index key problem: Indices shift on insert/delete/reorder → wrong state attached to wrong item

Good keys: Database IDs, UUIDs, or any stable unique identifier from the data

Bad keys: Array index, Math.random(), Date.now() (unstable across renders)

Performance: Stable keys let React move nodes; index keys force re-renders

Index keys OK when: Static list + no state + no reordering (all three required)