Skip to main content

Why is key needed in React?

key is a special React prop that uniquely identifies list items so the reconciliation algorithm can track additions, removals, and reorders without rebuilding the entire list.

Theory

TL;DR

  • Keys work like name badges at a conference: React uses them to match "old item" to "new item" across renders, instead of checking everyone by seat position.
  • Without keys, React diffs by position. Any reorder causes every item after the change to re-render.
  • With stable keys, React matches by identity and updates only what actually changed.
  • Static list, never reordered: index works. Dynamic list with reorders or local state: use a stable unique ID.

Quick example

jsx
// Without keys: React warns and diffs by position const BadList = ({ fruits }) => ( <ul> {fruits.map(fruit => <li>{fruit.name}</li>)} </ul> ); // With stable keys: React tracks each item by identity const GoodList = ({ fruits }) => ( <ul> {fruits.map(fruit => <li key={fruit.id}>{fruit.name}</li>)} </ul> );

Swap fruits[0] and fruits[1]: BadList re-renders all <li> elements after the change. GoodList just moves two nodes.

Why position-based diffing fails

React's reconciliation builds two trees (previous and next virtual DOM) and walks them to find the minimum DOM operations needed. For lists without keys, it matches elements by their index in the array. Fine while the order never changes.

Prepend an item. Every element's index shifts by one. React sees "new" items at every position and re-renders them all. Input values, focus state, scroll position inside those components: gone.

With keys, React reads the key first and matches old fiber nodes to new ones by identity. Only actually new or removed nodes get mounted or unmounted.

When to use stable keys

  • Static list, never reordered: index works.
  • Dynamic list with adds, removes, or reorders: use the item's database ID or another stable identifier.
  • Items with local state (inputs, checkboxes, expanded panels): always stable keys, or state jumps to the wrong component.
  • Nested lists: keys at every .map() level, not just the outer one.

I once worked on an e-commerce cart where quantity inputs reset to 1 every time a promo event triggered a reorder. Three characters fixed it: key={index} became key={item.variantId}.

Common mistakes

Index as key in a dynamic list

jsx
// Reorder items → state and focus jump to wrong elements {items.map((item, index) => <li key={index}>{item.text}</li>)}

The index changes when the array changes. React treats every post-change item as a different component. Fix: key={item.id}.

Non-unique keys

jsx
// Two users named "John" → React picks first match, second one remounts {users.map(user => <li key={user.name}>{user.name}</li>)}

Keys must be unique among siblings. Use a real ID, or build a composite: key={user.id + '-' + user.name}.

Random keys on every render

jsx
// Math.random() → new key every render → full remount every time {items.map(item => <li key={Math.random()}>{item.text}</li>)}

Generate stable IDs once when the data loads and store them in state. Never generate inside .map().

Missing keys in nested lists

jsx
{categories.map(cat => ( <div key={cat.id}> {cat.items.map(item => <span>{item}</span>)} {/* No key here */} </div> ))}

Every .map() needs keys, not just the outer level.

Real-world usage

  • Admin dashboards with table rows: row IDs as keys.
  • Material-UI DataGrid: the getRowId prop is the same concept at the component level.
  • React Native FlatList: keyExtractor serves the same role. Wrong keys cause visual glitches during virtualized scroll.
  • TanStack Table with infinite scroll: stable keys prevent duplicate content appearing on re-fetch.

Follow-up questions

Q: Why not just always use index?
A: For a list that never changes order and has no stateful items, index is fine. Add sorting, filtering, or prepending and index breaks state tracking.

Q: What if my data has no unique ID?
A: Generate one with crypto.randomUUID() or a uuid library when the data first loads. Keep it in state. Never generate it inside .map().

Q: How do keys affect hooks inside list items?
A: Keys do not change hook call order. But if the key changes, React unmounts and remounts the component, resetting all hook state. This is useful sometimes: changing a key is the cleanest way to force a full component reset.

Q: What algorithm does React use for list diffing?
A: The reconciler finds the longest increasing subsequence of keys to minimize the number of moves. Stable, sorted keys hit near O(n). Fully shuffled keys hit the worst case. You can observe this by logging getSnapshotBeforeUpdate during a reorder.

Examples

Static list (index as key is acceptable)

jsx
const MENU_ITEMS = ['Home', 'About', 'Contact']; function Nav() { return ( <nav> {MENU_ITEMS.map((label, index) => ( // Static, never reordered → index is fine <a key={index} href={`/${label.toLowerCase()}`}>{label}</a> ))} </nav> ); }

The list never changes, so no index ever shifts. No state to lose.

Dynamic list with local state (stable ID required)

jsx
function CartList({ lineItems }) { const [notes, setNotes] = React.useState({}); return ( <ul> {lineItems.map(item => ( <li key={item.variantId}> {/* Stable SKU ID */} <span>{item.name}</span> <input value={notes[item.variantId] || ''} onChange={e => setNotes({ ...notes, [item.variantId]: e.target.value })} placeholder="Add a note" /> </li> ))} </ul> ); }

When the cart reorders after a promo sort, each input keeps the note for its own item. With key={index}, the note for item 3 would appear on item 1 after sorting.

Short Answer

Interview ready
Premium

A concise answer to help you respond confidently on this topic during an interview.

Finished reading?