Computed Caching and Performance
A computed property in Vue is not just a convenient way to derive a value from other reactive state — it is a memoized, lazily-evaluated value backed by a dependency tracker. When you read a computed property, Vue checks whether any of the reactive sources it touched during its last evaluation have changed. If none have, it returns the cached value instantly without re-running the getter function. This is fundamentally different from calling a plain function in your template, which re-executes on every single render regardless of whether its inputs changed. Understanding this distinction is one of the highest-leverage performance skills in Vue development, because misusing computed properties (or using methods where computed properties belong) is one of the most common sources of unnecessary re-computation in real applications.
Cricket analogy: A computed property is like a scoreboard operator who only updates the run rate display when a run is actually scored, instantly showing the same cached number between deliveries, whereas a plain method is like an announcer who recalculates the run rate out loud after every single ball regardless of whether the score changed.
How dependency tracking drives the cache
When a computed getter runs, Vue's reactivity system records every reactive property it reads — refs, reactive object properties, other computed properties — as a dependency. The computed value is then marked dirty only when one of those exact dependencies is mutated. If your getter reads cart.items but never reads cart.discountCode, changing the discount code will not invalidate the cache, because that property was never tracked as a dependency in the first place. This fine-grained tracking, not a naive 'recompute on any state change' model, is what makes computed properties efficient even in components with large amounts of unrelated reactive state.
Cricket analogy: If a computed 'run rate' getter only reads the overs bowled and runs scored, changing the weather forecast display elsewhere on the broadcast won't invalidate the cached run rate, because Vue only tracked the specific properties actually read during the getter — like a scorer who ignores unrelated broadcast graphics.
Computed vs. method calls in templates
It is tempting to reach for a plain method when a template needs a derived value, especially since the syntax {{ formatPrice() }} looks almost identical to {{ formattedPrice }}. But a method invoked in a template re-runs on every re-render of the component, no matter the cause, while a computed property only re-runs when its tracked dependencies actually change. For cheap, trivial transforms the difference is negligible, but for anything involving array filtering, sorting, aggregation, or expensive string formatting across large lists, the gap becomes significant, especially inside components that re-render frequently due to unrelated prop or state changes.
Cricket analogy: Calling formatScore() as a method in the template is like asking the scorer to recalculate the entire tournament's net run rate from scratch after every single ball bowled anywhere in the stadium, even balls that don't involve this match, whereas a computed property only recalculates when this specific match's runs actually change.
<script setup>
import { ref, computed } from 'vue'
const orders = ref([
{ id: 1, total: 42.5, status: 'paid' },
{ id: 2, total: 18.0, status: 'pending' },
{ id: 3, total: 96.2, status: 'paid' },
])
// Cached: only recomputes when `orders` changes.
const paidTotal = computed(() => {
console.log('recalculating paidTotal')
return orders.value
.filter((order) => order.status === 'paid')
.reduce((sum, order) => sum + order.total, 0)
})
// Not cached: re-runs on every render, even ones triggered
// by unrelated state elsewhere in the component.
function paidTotalMethod() {
console.log('recalculating via method')
return orders.value
.filter((order) => order.status === 'paid')
.reduce((sum, order) => sum + order.total, 0)
}
</script>
<template>
<p>Computed: {{ paidTotal }}</p>
<p>Method: {{ paidTotalMethod() }}</p>
</template>Vue's caching applies to reads, not to the number of times a computed property is used in the template. You can reference the same computed property ten times across a component and the getter still only runs once per invalidation — the other nine reads are served from cache. This extends to chained computed properties too: each depends only on what it actually reads, so a change deep in the source state invalidates only the computed properties that depend on the affected path, propagating no further than necessary.
A common pitfall is writing a computed getter that reads from a non-reactive source, such as Date.now(), Math.random(), or a value pulled from localStorage outside of Vue's reactivity. Since these are not tracked dependencies, the computed property will cache its very first result forever and never update, which looks like a bug in the computed system but is actually a violation of its core assumption: getters must be pure functions of reactive state.
- Computed properties cache their return value and only recompute when one of their tracked reactive dependencies changes.
- Methods called in templates re-run on every render, regardless of whether their underlying data changed.
- Dependency tracking is fine-grained: only reading a reactive property during evaluation registers it as a dependency.
- Chained computed properties each maintain their own cache, so invalidation only propagates as far as necessary.
- Computed getters must be pure — reading non-reactive sources like Date.now() breaks caching because Vue never sees a reason to invalidate.
- Prefer computed properties over template methods for any non-trivial derived value, especially filtering, sorting, or aggregation over larger collections.
Practice what you learned
1. Why does a computed property avoid recomputing its value on every component re-render?
2. What happens if `{{ getTotal() }}` (a plain method) is used in a template instead of a computed property?
3. A computed getter calls `Math.random()` and no reactive refs. What will happen after the component re-renders many times?
4. If computed property B depends on computed property A, and an unrelated ref C changes (which neither A nor B reads), what happens?
5. Which use case benefits most from being written as a computed property rather than a method call in the template?
Was this page helpful?
You May Also Like
Computed Properties
How Vue's computed() function derives cached, reactive values from other state, and why it's preferred over methods for derived data in templates.
Watchers Explained
How watch() and watchEffect() let you run side effects in response to reactive state changes, and how to choose between them.
reactive() and ref()
A deep dive into Vue 3's two core reactivity primitives — ref() for any value type and reactive() for objects — and when to use each.
Lazy Loading and Code Splitting
Covers how to split a Vue application into smaller chunks using dynamic imports for routes and components, reducing initial bundle size and improving load performance.
Related Reading
Related Study Notes in Web Development
Browse all study notesWebSockets Study Notes
Web Development · 30 topics
Web DevelopmentWebAssembly Study Notes
WebAssembly · 30 topics
Web DevelopmentgRPC Study Notes
Protocol Buffers · 30 topics
Web DevelopmentSpring Boot Study Notes
Java · 30 topics
Web DevelopmentFlask Study Notes
Python · 30 topics
Web DevelopmentDjango Study Notes
Python · 30 topics