Updated June 2026. Tested on Vue 3 with <script setup>. Part of the Techalyst Vue 3 series.

A computed property is not stored data, it is a cached getter. You read it like a property, but behind it is a function whose result Vue caches and only recomputes when one of the reactive values it depends on changes. That caching is what makes computed the right tool for derived values, and a few rules keep it from biting you.

The basics

In <script setup>, create one with computed(). It returns a ref-like value, so you read it with .value in script and plainly in the template.

<script setup>
import { reactive, computed } from 'vue'

const car = reactive({ brand: 'Toyota', year: 2002 })

const carAge = computed(() => new Date().getFullYear() - car.year)
</script>

<template>
  <p>{{ carAge }}</p>
</template>

Because car is reactive, changing car.year recomputes carAge automatically and re-renders. You never assign to a computed (unless you give it a setter); it derives its value from its dependencies.

Caching: the whole point

A computed getter does not run on every read. It runs once, caches the result, and only re-runs when a tracked dependency changes. Read it ten times with no change in between and the function runs once.

carAge.value   // getter runs
carAge.value   // cached, getter does not run
car.year = 1980
carAge.value   // dependency changed, getter runs again

This is the difference from a method: a method runs every time you call it, a computed runs only when its inputs change. For a value derived from reactive state and read in several places, computed is both cleaner and faster. There is a subtlety worth knowing: changing a dependency does not immediately re-run the getter, it marks the cache stale and the getter re-runs on the next access.

Getter and setter

Most computed are getters only. When you do need to write to one, use the object form with get and set.

import { ref, computed } from 'vue'

const message = ref('hello world')

const shout = computed({
  get: () => message.value.toUpperCase(),
  set: (value) => { message.value = value },
})

shout.value = 'vue is great'  // setter runs, message.value updates too

The two stay in sync: change message and shout reflects it; assign to shout and the setter writes back to message.

The golden rule: a getter must be pure

Never mutate state inside a computed getter. Vue may call a getter at any time, so a side effect inside one produces values that change for no visible reason.

// bad: mutates state on every read
const age = computed(() => {
  count.value++                          // never do this
  return new Date().getFullYear() - year.value
})

// good: reads only
const age = computed(() => new Date().getFullYear() - year.value)

If count is shown somewhere, it would silently increment every time age is read. Keep getters read-only.

Copy before you sort

This is the most common real bug. Many array methods mutate in place, so calling them on reactive state inside a getter quietly rewrites your source.

// bad: .sort() reorders the original array
const sorted = computed(() => items.sort((a, b) => a.year - b.year))

// good: copy first, then sort
const sorted = computed(() => [...items].sort((a, b) => a.year - b.year))

Watch for .sort(), .reverse(), .splice(), .push(), .pop(), .shift(), .unshift(). Always copy with [...arr] or arr.slice() first. The copy is shallow (the elements are the same object references), so reorder freely but mutate element values through the original array, not the copy.

The classic use: sorting a v-for list

v-for renders an array in its existing order. To show a custom order without touching the source, point v-for at a computed sorted copy.

<script setup>
import { reactive, computed } from 'vue'

const items = reactive([
  { title: 'Blade Runner', year: 1982 },
  { title: 'Dune', year: 2021 },
  { title: 'Alien', year: 1979 },
])

const byYear = computed(() => [...items].sort((a, b) => a.year - b.year))
</script>

<template>
  <li v-for="item in byYear" :key="item.title">{{ item.title }} ({{ item.year }})</li>
</template>

Change any element through the original items and the computed re-sorts and re-renders on its own.

Two tracking gotchas

Vue tracks every reactive value that appears in the getter's scope, even ones that do not affect the result. So referencing a reactive value you do not use still makes the getter re-run when it changes. Only touch the reactive values you actually need.

The early-return trap. If a getter exits early, Vue only tracks the reactive values it reached before the return. Anything after the early return is invisible to Vue on that run, so changes to it will not trigger a recompute. And non-reactive sources (a plain object, localStorage, a module variable) are never tracked at all, so the cache goes stale silently. If a value must influence a computed, make it reactive with ref or reactive.

Wrapping up

computed() gives you a cached, reactive derived value: it recomputes only when a dependency changes, which beats a method that runs every call. Keep getters pure, copy arrays before sorting, and use a computed as your v-for target to sort without mutating the source. Watch the tracking gotchas, only reference reactive values you need, avoid early returns that hide dependencies, and keep non-reactive data out of getters.

More in the series: Vue 3 reactivity and watchers. Questions welcome below.