skip to content

In Vue 3, what does v-for do when a list rendered without :key changes order, and when is that default safe?

level: middleimportance: must knowfreq 78%

answer

  1. patch by position
  2. DOM nodes stay where they are
  3. stateless rows only
  4. primitive, stable, unique ids

basics

~20 s

Without :key, Vue 3 patches each v-for element in place by position instead of moving DOM nodes; that is only safe when rows hold no component state or DOM state such as input values or focus.

solid answer

~40 s

Without `:key`, Vue uses an **in-place patch**: after a reorder it keeps the existing DOM nodes where they are and updates each one to show whatever item now sits at that index. That is cheap, and correct for plain text rows, but any state not derived from the item stays with the **position**: a typed input value, focus, a checked box that is not bound, or a child component's local refs. With `:key="item.id"` Vue matches old and new nodes by key, moves them, and destroys nodes whose keys disappeared. Keys should be **unique primitives** (string, number, symbol) that stay with the item. On `<template v-for>` in Vue 3 the key goes on the `<template>` tag.

go deeper

for a junior

Remember to add :key with a stable id from the data on every v-for that renders inputs or components.

for a middle

Explain the in-place patch: nodes stay by position and only their contents change, so state not derived from the item stays with the position.

for a senior

Recognise the symptoms of missing or index keys in bug reports, such as values or focus jumping rows after a sort, and fix them with data ids.

for a principal

Set a team rule, enforced by linting, that list keys come from domain ids and that unkeyed lists are a deliberate, commented choice.

## The default: in-place patch When a `v-for` list re-renders, Vue compares the new list of virtual nodes with the previous one. If the nodes carry **no key**, Vue uses what its docs call the **in-place patch** strategy: it walks the two lists by position, reuses the element at index 0 for the new item at index 0, the element at index 1 for the new item at index 1, and so on. It then patches only what differs (text, attributes, props), adds nodes at the end if the list grew and removes surplus nodes if it shrank. Nothing is moved. This is efficient, and for many lists it is perfectly correct. The docs are precise about the limit: the default is **only suitable when the list output does not rely on child component state or temporary DOM state**. ## What goes wrong without keys State that is not derived from the item stays attached to the **DOM node or component instance at a position**, not to the item. After a reorder, insertion at the top or removal from the middle, that state appears next to a different item: - the text typed into an uncontrolled `<input>`; - which element has **focus** or a text selection; - a child component's local `ref` state, since the instance at that position is reused and only its props change; - a running CSS transition or an embedded media element's playback position. ## What :key changes `key` is a special attribute bound with `v-bind`. With keys present, Vue builds a map from key to new position and matches old nodes to new ones **by key**: 1. Nodes whose key is still present are patched and **moved** to their new position. 2. Nodes whose key disappeared are **unmounted**, running their cleanup. 3. Nodes with new keys are **mounted** fresh. | | No `:key` | `:key="item.id"` | |---|---|---| | Matching | by index | by key | | After a reorder | nodes stay, contents rewritten | nodes move with their item | | Local/DOM state | stays with the position | stays with the item | | Removed item | the last node is removed | that item's node is removed | ## Choosing a key - **Unique among siblings.** Duplicates break the keyed match; in development Vue logs `Duplicate keys found during update` when it meets them while diffing. - **Stable**: the same item must produce the same key on every render. `Math.random()` or a fresh `crypto.randomUUID()` per render remounts every row every time. - **Primitive**: the API expects `number | string | symbol`; the guide says not to use objects. - **Taken from the data**, typically a database id. The array index is a position, not an identity, so an index key reproduces the in-place behaviour for reorders. ## When the default is fine The docs recommend a key "whenever possible", with two exceptions: the iterated content is simple (no components, no stateful elements), or you **deliberately** want the in-place behaviour for speed. A list of plain `<li>{{ tag }}</li>` strings is the classic safe case. ## Vue-specific details - Vue 3 does not warn at runtime about a missing key; linting is where that check usually lives. - On `<template v-for>`, the `:key` goes on the `<template>` tag. The compiler reports a key placed on its child. - Keys also force replacement outside lists: changing the `key` of a single element or component makes Vue unmount the old one and mount a new one. The framework-neutral reason keys give identity is a separate topic; the Vue-specific points here are the default's name and limits, where the key goes, and what a valid key looks like.

  • In Vue 3, is `:key="index"` any better than no key for a list that gets reordered?
    Not for identity. The index is a position, so after a reorder the same index keys map to the same DOM nodes and state stays by position, just as with the in-place patch. Index keys are harmless only for lists that never reorder, insert or remove except at the end.
  • What happens in Vue 3 if every render assigns each `v-for` row a new random key?
    No old key ever matches a new one, so Vue unmounts every row and mounts new ones on each render. Inputs lose their values and focus, child components reset their state and rerun setup, and the list does far more DOM work than needed.
  • Where does `:key` go when `v-for` is on a `<template>` in Vue 3?
    On the `<template>` tag itself, next to `v-for`. The compiler reports a key placed on a child element with `<template v-for> key should be placed on the <template> tag.`

Without keys Vue behaves like a theatre that keeps actors in their seats and hands each seat a new script after a reshuffle; the words change, but each actor's props stay in their seat.

saying these in an interview costs you the question

  • Vue moves DOM nodes to follow the data even without keys.
  • Using the array index as :key fixes state after reordering.
  • Keys are only a performance hint and never affect correctness.
  • A fresh random key per render is a safe way to guarantee uniqueness.
  • Vue 3 throws at runtime when a v-for element has no key.