Explain why :key is required/recommended in v-for, what Vue uses it for during virtual DOM diffing, and give concrete examples of bugs you can get when keys are missing or unstable (including why using the index as key is usually wrong).
Why are keys critical in v-for, and what breaks without them?
Use guided tracks for structured prep, then practice company-specific question sets when you want targeted interview coverage.
Core idea:key gives Vue a stable identity for each rendered item. During updates, Vue matches old VNodes to new VNodes primarily by type + key. With good keys, Vue can move/reuse the right DOM nodes and component instances. Without keys, Vue falls back to a mostly index-based / in-place patch strategy, which can reuse the wrong DOM/component for a different item.
Scenario | With stable keys (e.g., item.id) | Without keys / bad keys (e.g., index) |
|---|---|---|
Insert/remove in the middle | Vue inserts/removes the correct node and keeps other items' DOM/state attached | Vue reuses DOM nodes by position; DOM/state may “shift” to the wrong item |
Sorting / reordering | Vue moves existing nodes (minimal DOM work) and preserves per-item state | Vue patches in place; items can display wrong state (inputs, toggles, child state) |
Stateful child components in a list | Each child instance stays with its data item | Child instances can get reassigned to different data items (state leak/mismatch) |
Transitions ( | Correct enter/leave/move animations | Animations glitch or don’t run correctly (Vue can’t track moves reliably) |
What “breaks” in practice
Typical real bugs when keys are missing or unstable:
• Inputs show the wrong value after sorting/filtering because the DOM input element is reused for a different item.
• Focus/caret jumps (typing in an input, list updates, and the focused DOM node is reused/moved incorrectly).
• Checkboxes/toggles flip on the wrong row (DOM state vs data association breaks).
• Child component local state leaks (e.g., expanded/collapsed, internal caches, timers) to a different item after reordering.
• Transition-group move animations break because Vue can’t reliably compute “this node moved from A to B” without keys.
<!-- BAD: no key (or :key="index") can cause DOM/state to shift on reorder -->
<script setup>
import { ref } from 'vue';
const items = ref([
{ id: 1, name: 'Alpha' },
{ id: 2, name: 'Bravo' },
{ id: 3, name: 'Charlie' }
]);
function reverse() {
items.value = [...items.value].reverse();
}
</script>
<template>
<button @click="reverse">Reverse</button>
<ul>
<li v-for="(item, index) in items">
<!-- Try typing in Alpha, then Reverse: the typed DOM input may appear under a different item -->
<label>
{{ item.name }}
<input placeholder="type here" />
</label>
</li>
</ul>
</template>
<!-- GOOD: stable key preserves DOM association and component instances -->
<script setup>
import { ref } from 'vue';
const items = ref([
{ id: 1, name: 'Alpha' },
{ id: 2, name: 'Bravo' },
{ id: 3, name: 'Charlie' }
]);
function reverse() {
items.value = [...items.value].reverse();
}
</script>
<template>
<button @click="reverse">Reverse</button>
<ul>
<li v-for="item in items" :key="item.id">
<label>
{{ item.name }}
<input placeholder="type here" />
</label>
</li>
</ul>
</template>
Index as key: when it’s wrong vs acceptable:key="index" is effectively “no stable identity” when the list can reorder, filter, insert, or delete. It tells Vue: “identity is position”, so Vue will keep DOM/state by index, not by data item. It’s only acceptable when the list is truly static (never changes order/length) or when each row has no meaningful state (rare in real apps).
Key rule | Why | Example |
|---|---|---|
Use a stable unique id | Preserves identity across reorder/insert/delete | :key="item.id" |
Don’t use random/unstable keys | Forces full destroy/recreate every render (kills performance/state) | :key="Math.random()" (bad) |
Avoid index for dynamic lists | Index changes when list changes, so identity shifts | :key="index" (usually bad) |
Key must be primitive + stable | Vue compares keys; stable primitives keep matching predictable | number/string id |
transition-group needs keys | Move/enter/leave tracking depends on identity | <transition-group> children must be keyed |
Interview-ready takeaway
Keys aren’t about “performance only”. They’re primarily about correctness: ensuring DOM nodes and component instances stay attached to the same logical data item across updates. Without stable keys (or with index keys), you risk state/DOM mismatches when the list changes.