Directly manipulating the real browser DOM is comparatively slow — every change can trigger layout recalculation and repainting. React keeps a lightweight in-memory tree of React elements (informally, "the virtual DOM"), and on every state change:
This is why a re-render isn't inherently expensive — re-running a JavaScript function is cheap. It's unnecessary real DOM writes that are slow, and diffing is precisely what avoids them.
When diffing a list, React needs a stable way to match "this element in the new list" to "this element in the old list," so it can tell inserts and removals apart from a plain reorder:
{users.map((u) => (
<li key={u.id}>{u.name}</li>
))}
key must be stable and unique among siblings — a database ID is ideal. Using the array index as a key is a common bug when the list can be reordered, filtered, or have items inserted/removed from the middle: React matches by position, so it can end up reusing the wrong DOM node's internal state (an input's cursor position, a component's useState) for what is now a different logical item, since the index didn't change even though the underlying data did.
<div> becomes <span>, or <ComponentA /> becomes <ComponentB />), React tears down the old subtree entirely (including its state) and builds a fresh one — it does not try to diff across different types.