skip to content

In Vue 3, what kinds of values can a v-for directive iterate, and which aliases does each form give the template?

level: juniorimportance: should knowfreq 58%

answer

  1. more than just arrays
  2. objects get three aliases
  3. a number counts from one
  4. Map entries arrive as pairs

basics

~10 s

Vue 3's v-for iterates arrays (item, index), plain objects (value, key, index), an integer n (1 to n, plus a 0-based index), strings, and any iterable such as Map or Set.

solid answer

~40 s

`v-for` takes `alias in source` (or `of`). An **array** gives `(item, index)`; a **plain object** gives `(value, key, index)` in `Object.keys()` order; an **integer** `n` repeats the template with values `1..n` and a 0-based index; a **string** yields its characters; any **iterable** such as `Map` or `Set` is spread with `Array.from`, so a `Map` yields `[key, value]` pairs you can destructure. Aliases can be destructured like function parameters and are only visible inside the element. `<template v-for>` repeats several siblings without a wrapper, and on a component the item is **not** passed automatically: you bind it as a prop, with a `:key`.

code

vue · 21 lines
vue
<script setup lang="ts">
import { ref } from 'vue'

const users = ref([{ id: 1, name: 'Ada' }, { id: 2, name: 'Linus' }])
const profile = ref({ title: 'Engineer', city: 'Oslo' })
const roles = new Map([['ada', 'admin'], ['linus', 'viewer']])
</script>

<template>
  <ul>
    <li v-for="({ id, name }, index) in users" :key="id">{{ index }}: {{ name }}</li>
  </ul>
  <dl>
    <template v-for="(value, key, index) in profile" :key="key">
      <dt>{{ index }}. {{ key }}</dt>
      <dd>{{ value }}</dd>
    </template>
  </dl>
  <span v-for="n in 3" :key="n">{{ n }}</span>
  <p v-for="[login, role] in roles" :key="login">{{ login }} is {{ role }}</p>
</template>

go deeper

for a junior

Recall the array and object alias shapes, that a number range starts at 1, and that <template v-for> repeats several siblings without a wrapper.

for a middle

Explain that aliases behave like callback parameters: scoped, destructurable, not writable, and that a component needs the item passed as a prop.

for a senior

Point out the ordering and typing traps: object order is property order, Map items are entry pairs, and v-model needs a member expression to write back.

for a principal

Argue for normalising data into keyed arrays at the boundary so templates never depend on object order or ad hoc iteration forms.

## What v-for is `v-for` is Vue's built-in directive for rendering a block once per entry of a source value. Its expression has the special form `alias in source`, and Vue also accepts `alias of source` so it reads like a JavaScript `for...of`. The compiler turns the directive into a call to an internal list helper that walks the source and renders the element for every entry. What the aliases contain depends on the **type of the source**. ## The five source forms | Source | Syntax | Aliases | |---|---|---| | Array | `(item, index) in items` | element, 0-based position | | Plain object | `(value, key, index) in obj` | property value, property name, position | | Integer `n` | `(n, i) in 10` | `1..n`, 0-based position | | String | `(ch, i) in 'abc'` | each character, position | | Iterable (`Map`, `Set`, generators) | `([k, v], i) in map` | each iterated value, position | A few details interviewers like to check: - **Objects** are walked in the order `Object.keys()` returns, which is standard JavaScript property order (integer-like keys ascending, then string keys in insertion order), not a guaranteed business order. - **Ranges start at 1**, not 0: `v-for="n in 3"` renders 1, 2, 3. In development a negative or non-integer number logs a warning that the range expects a positive integer; `0` simply renders nothing. - **Map** is iterated as entries, so each item is a `[key, value]` pair. Destructure it in the alias. - **Destructuring** works exactly like function parameters: `v-for="({ id, name }, i) in users"`. ## Scope of the aliases The aliases behave like the parameters of a `forEach` callback: they exist only inside the element carrying `v-for` and its children, while everything from the component's scope stays readable. Nested `v-for` works like nested functions, so an inner loop can read the outer alias. Because an alias is a parameter, it is **not writable**. In an SFC build, `v-model="item"` on a bare alias is a compile error (`v-model cannot be used on v-for or v-slot scope variables because they are not writable.`). Bind to a property of the item (`v-model="item.name"`) or to the array slot (`v-model="items[index]"`) instead. ## Repeating more than one element To repeat a group of siblings with no wrapper element, put `v-for` on a `<template>` tag. The `<template>` itself renders nothing. In Vue 3 the `:key` belongs on the `<template>` tag; placing it on a child instead is reported by the compiler with `<template v-for> key should be placed on the <template> tag.` ## v-for on a component A component can carry `v-for` like any element, but the iterated item is **not injected** into it. Components have isolated scopes, so data must go in through props: 1. Put `v-for` and `:key` on the component tag. 2. Pass the item (and the index if needed) as props. 3. Declare those props in the child with `defineProps`. The docs give the reason: auto-injecting the item would couple the child to how it happens to be rendered, while explicit props keep it reusable elsewhere. ## Common mistakes - Expecting `n in 5` to start at 0. - Treating a `Map` item as the value rather than an entry pair. - Relying on object iteration for a meaningful order; sort an array instead. - Expecting the loop item to be visible inside a child component without a prop. - Writing to the alias with `v-model`. The `:key` on each form, and why it matters, is a separate subject: every example above that renders components or stateful elements should carry a stable, unique key.

  • Why does `v-model="item"` inside a Vue 3 `v-for` fail to compile in an SFC?
    The alias is like a callback parameter: assigning to it would not change the array. The compiler rejects `v-model` on a bare `v-for` or `v-slot` scope variable. Bind to `item.name` when items are objects, or to `items[index]` when they are primitives, so the write reaches the reactive source.
  • How would you render a Vue 3 `v-for` over an object in a specific order, say alphabetically by key?
    Do not rely on object iteration order, which follows `Object.keys()` property order. Build a computed array such as `Object.entries(obj).sort(([a], [b]) => a.localeCompare(b))` and iterate that with `([key, value]) in sortedEntries`, keyed by `key`.

saying these in an interview costs you the question

  • A v-for range such as n in 5 starts at 0.
  • The v-for item is automatically available inside a child component.
  • v-for only accepts arrays, so a Map must be converted first.
  • Object iteration in v-for follows a guaranteed business order.
  • v-model can bind directly to the v-for alias of a primitive.