How to Provide a Unique React Key for Array Children: Best Practices and Performance Optimization

Use a stable, intrinsic identifier (like item.id) as the key prop when rendering lists in React to enable O(N) reconciliation, preserve component state, and prevent unnecessary DOM operations.

When rendering dynamic lists in the facebook/react repository, providing a unique React key for array children is essential for the reconciler to efficiently update the UI. Without stable keys, React cannot distinguish between moved, added, or removed items, leading to degraded performance and potential state bugs. This guide explains the internal mechanics of key validation and the exact best practices derived from the React source code.

Why a Unique React Key Matters for Array Children

React reconciles UI updates by comparing the current component tree with the previous one. When a component renders an array of children, the key prop lets the reconciler quickly match each child to its previous instance using an internal map.

Providing a unique React key for array children enables three critical optimizations:

  1. Identify moved, added, or removed items without re-creating every element.
  2. Preserve component state (e.g., useState inside a list item) because the same component instance is reused.
  3. Avoid unnecessary DOM operations, which boosts rendering performance, especially for large lists.

According to the React source code, missing or duplicate keys force the reconciler to fall back to a full diff, increasing work by O(N²) in worst-case scenarios. Stable keys restore the expected O(N) reconciliation cost.

How React Validates Keys Internally

In development mode, React validates that each sibling has a unique "key" prop and throws a warning if it does not. The validation lives in the reconciler at:

packages/react-reconciler/src/ReactChildFiber.js

The relevant code emits the warning:

console.error(
  'Each child in a list should have a unique "key" prop.' +
  '%s%s See https://react.dev/link/warning-keys for more information.',
  currentComponentErrorInfo,
  childOwnerAppendix,
);

(source: ReactChildFiber.js L221-L227)

This warning appears when React detects children with duplicate or missing keys during the reconciliation process.

Best Practices for Unique React Keys in Arrays

Use Stable Intrinsic Identifiers

Always prefer a value that uniquely identifies the data item, such as item.id from a database. This ensures the key remains consistent across renders regardless of array position.

function TodoList({todos}: {todos: Array<{id: string; text: string}>}) {
  return (
    <ul>
      {todos.map(todo => (
        <li key={todo.id}>{todo.text}</li>   // ✅ stable unique key
      ))}
    </ul>
  );
}

Avoid Using Array Index as a Key

Using key={index} is only acceptable when the list never changes order, never inserts or removes items, and the items have no internal state. In all other cases, the index causes state leakage and extra re-renders because React cannot distinguish between items when the array shifts.

// ❌ Bad: using index as key when items can reorder
items.map((item, index) => <li key={index}>{item.name}</li>);

Never Generate Random Keys on Render

Generating keys with Math.random() or Date.now() on every render defeats the purpose of keys. Because the key changes on every render, React treats each element as new, destroying and recreating DOM nodes unnecessarily.

// ❌ Bad: random key on every render
items.map(item => <li key={Math.random()}>{item.name}</li>);

Prefer Primitive Key Values

React stringifies keys internally. Using a primitive (string or number) avoids extra object allocation and ensures predictable comparison behavior.

Handling Lists Without Natural IDs

When you don't have a natural ID:

  1. Use the useId hook (React 18+) to generate a stable, globally unique prefix, then combine it with an index that is stable for that render cycle.
import {useId} from 'react';

function RandomItemList({items}: {items: string[]}) {
  const idPrefix = useId();               // stable across renders
  return (
    <ul>
      {items.map((item, i) => (
        <li key={`${idPrefix}-${i}`}>{item}</li> // ✅ deterministic key
      ))}
    </ul>
  );
}
  1. Transform data upstream to include a deterministic ID before it reaches the component.

Working with React.Children.toArray

The React.Children.toArray helper clones each child and automatically assigns a key if none is provided, using the child's original key or its position. This is useful when manipulating children (e.g., filtering, reversing) while preserving keys.

function ReversedList({children}: {children: React.ReactNode}) {
  const normalized = React.Children.toArray(children);
  const reversed = normalized.slice().reverse(); // preserves keys
  return <>{reversed}</>;
}

/* Usage */
<ReversedList>
  <Item key="a" />
  <Item key="b" />
  <Item key="c" />
</ReversedList>

(source: ReactChildren-test.js L830-L836)

Common Pitfalls to Avoid

When providing a unique React key for array children, avoid these anti-patterns:

// ❌ Bad: using index as key when items can reorder
items.map((item, index) => <li key={index}>{item.name}</li>);

// ❌ Bad: random key on every render
items.map(item => <li key={Math.random()}>{item.name}</li>);

These patterns force React to destroy and recreate components, losing internal state and causing significant performance degradation.

Summary

  • Always provide a unique React key for array children using stable, intrinsic identifiers like database IDs.
  • Never use array indices as keys unless the list is static and stateless.
  • Avoid random or generated keys on every render to prevent unnecessary component unmounting.
  • Prefer primitive values (strings or numbers) for optimal reconciliation performance.
  • Use React.Children.toArray when transforming child collections to preserve existing keys.
  • Reference packages/react-reconciler/src/ReactChildFiber.js for the internal validation logic that warns about missing keys.

Frequently Asked Questions

Can I use the array index as a key in React?

You should only use the array index as a key when the list is completely static—meaning items never reorder, are never inserted or removed, and contain no internal state. In dynamic lists, using key={index} causes React to confuse component identities when the array shifts, leading to state bugs and unnecessary re-renders.

What happens if I don't provide a key prop in React lists?

If you omit the key prop, React defaults to using the array index as the key. In development mode, React checks for missing keys in packages/react-reconciler/src/ReactChildFiber.js and emits a console error: "Each child in a list should have a unique 'key' prop." This warning indicates that reconciliation may be inefficient and state may not persist correctly across renders.

Is it safe to use Math.random() for React keys?

No, generating keys with Math.random() or Date.now() is unsafe and counterproductive. Because these values change on every render, React treats every list item as a new component, destroying and recreating DOM nodes and losing all internal state. This forces the reconciler into an O(N²) diff algorithm, severely degrading performance.

How does React use keys during reconciliation?

During the reconciliation phase, React builds a map of existing children keyed by their key value. When processing updates, React uses these keys to match current children with their previous instances in O(1) time. This allows React to identify which items moved, were added, or were removed, enabling the O(N) reconciliation algorithm. Without stable keys, React cannot perform this efficient mapping and must resort to slower comparison strategies.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →