Skip to the solution
MediumFrontend10 min read

Why is the :key attribute important in v-for?

111 views

The problem

Question presented to candidate: "Why does Vue want a :key on every v-for? What actually goes wrong if you leave it out or use the array index, and when is the index fine?"

What a strong answer should cover:

  • Without a key, Vue patches list children in place by position (an "unkeyed" diff); with keys it matches old and new children by identity and moves, creates or removes them.
  • Position matching is fine for plain text, but local state (component refs, input values, focus, transitions) stays with the position, not the item, when the list is reordered or an item is removed.
  • :key="index" is effectively the same as no key for reorders and removals.
  • Keys must be unique among siblings and stable across renders: database ids, not Math.random() or new objects.
  • In Vue 3 the key on <template v-for> goes on the <template> tag; duplicate keys produce a dev warning.
  • Changing a key on purpose forces a remount, a common reset technique.

Clarifying questions expected:

  • "Do the list items have local state, such as inputs or child components?"
  • "Can the list be reordered, filtered or have items removed from the middle?"

Code / implementation expected: Yes β€” a list of stateful rows rendered with index keys and id keys, showing state attached to the wrong item after a removal.

  • key
  • v-for

Try it yourself

Sketch your approach before you read the solution. That is what the interview tests.

Stuck? Get a hint.

A short nudge toward the approach. It will not give the answer away.

Solution

The solution is hidden so you can try first

10 min read Β· 10 sections

Read the code

VueIndex key vs id key: local row state follows the wrong item
<script setup>
import { defineComponent, h, ref } from "vue";

// A row with LOCAL state the parent does not know about: a "done" toggle.
const TodoRow = defineComponent({
  props: { text: String },
  setup(props) {
    const done = ref(false);
    return () =>
      h("li", { class: done.value ? "done" : "" }, [
        h("button", { onClick: () => (done.value = !done.value) }, done.value ? "[x]" : "[ ]"),
        " " + props.text,
      ]);
  },
});

const todos = ref([
  { id: 1, text: "Buy milk" },
  { id: 2, text: "Walk dog" },
  { id: 3, text: "Pay rent" },
]);

const removeFirst = () => todos.value.shift();
const reverse = () => todos.value.reverse();
// Changing a key on purpose throws the old subtree away: a full reset.
const generation = ref(0);
const reset = () => {
  generation.value++;
  todos.value = [
    { id: 1, text: "Buy milk" },
    { id: 2, text: "Walk dog" },
    { id: 3, text: "Pay rent" },
  ];
};
</script>

<template>
  <p>Tick the first item in both lists, then remove it or reverse.</p>
  <button id="remove" @click="removeFirst">remove first</button>
  <button id="reverse" @click="reverse">reverse</button>
  <button id="reset" @click="reset">reset</button>

  <h4>:key="index" (wrong)</h4>
  <ul id="by-index" :key="'index-' + generation">
    <TodoRow v-for="(todo, index) in todos" :key="index" :text="todo.text" />
  </ul>

  <h4>:key="todo.id" (right)</h4>
  <ul id="by-id" :key="'id-' + generation">
    <TodoRow v-for="todo in todos" :key="todo.id" :text="todo.text" />
  </ul>
</template>

Discussion

Other approaches, follow-ups and how it went in real interviews.

Sign in to join the discussion.

No comments yet. Share how you would answer it.

Question 70 of 92 in Vue.js

Keep your momentum going.

Mark this question solved to track your progress, then take the next one while it is fresh.

All Vue.js questions
Up nextMedium

How do dynamic components work?

Continue