Is It Bad Practice to Use a Computed Property Inside Another Computed Property in Vue 3?

No, nesting computed properties in Vue 3 is fully supported and considered idiomatic for composing reactive state, provided you avoid circular dependencies and keep getters pure.

The vuejs/core repository implements computed properties as first-class reactive primitives designed explicitly for this pattern. When you access one computed property inside another, Vue 3's reactivity system automatically establishes a subscription link between them, enabling efficient, lazy-evaluated dependency chains.

How Vue 3 Handles Nested Computed Properties

Vue 3 treats computed properties as special subscribers that track reactive dependencies through a sophisticated flag-based system. Understanding this mechanism confirms why nesting is architecturally sound.

The ComputedRefImpl Architecture

When you call computed(), Vue creates a ComputedRefImpl instance defined in packages/reactivity/src/computed.ts. This object stores the getter function, a Dep object for tracking subscribers, and internal flags (DIRTY, TRACKING, etc.) that manage caching state. The implementation ensures that computed properties behave both as reactive data sources (when read) and as reactive consumers (when executing their getters).

Automatic Dependency Tracking

When an outer computed property's getter reads the .value of an inner computed, the system invokes Dep.track() from packages/reactivity/src/dep.ts. This operation links the outer computed as a subscriber to the inner computed's Dep object. According to the vuejs/core source code, this creates a directed acyclic graph of reactive dependencies where changes propagate efficiently downstream.

Lazy Re-evaluation via refreshComputed

The refreshComputed() function in packages/reactivity/src/effect.ts drives the recomputation logic. When you access a nested computed chain, Vue checks the EffectFlags.DIRTY flag. If any upstream dependency—including an inner computed property—has a newer version, the outer property re-evaluates. Because evaluation is lazy, the getter runs only when .value is accessed, preventing wasteful recalculations during intermediate state changes.

When Nested Computed Properties Cause Problems

While the pattern is generally safe, three specific anti-patterns can introduce bugs or performance issues.

Circular Dependencies

Circular references occur when computed property A depends on B, which depends on A. Vue detects this scenario using the EffectFlags.RUNNING flag during refreshComputed. When a computed attempts to re-evaluate while already running, the system returns early to prevent stack overflow, but the result will be stale. Development builds emit a warning when this happens.

import { ref, computed } from 'vue'

const count = ref(1)

// BAD: Circular dependency
const c1 = computed(() => c2.value + 1)
const c2 = computed(() => c1.value + 1)

console.log(c1.value) // Undefined with dev warning

Break cycles by extracting shared logic into plain functions or non-computed refs.

Side Effects in Getters

Computed getters must remain pure functions. Side effects (like API calls or mutations) break Vue's caching guarantees and may execute unpredictably during dependency tracking. The reactivity system assumes getters are deterministic, so side effects do not integrate with the Dep notification system.

Deep Synchronous Chains

Nesting many computed layers with heavy synchronous work can cause cascade re-computations when deep dependencies change. While Vue's lazy evaluation minimizes overhead, accessing the outermost property still triggers evaluation of the entire chain. For expensive transformations across multiple layers, consider using watch or memoization utilities.

Practical Examples

This pattern demonstrates idiomatic composition where an outer computed automatically subscribes to an inner one.

import { ref, computed } from 'vue'

const base = ref(2)

const double = computed(() => base.value * 2)
const triple = computed(() => double.value + base.value)

console.log(triple.value) // 6
base.value = 3
console.log(triple.value) // 9 (double recomputed, then triple)

The outer triple tracks double's Dep. When base changes, double marks itself dirty; the next read of triple.value triggers fresh evaluation of both levels.

Circular Dependency (Avoid)

import { ref, computed } from 'vue'

const a = ref(1)

const c1 = computed(() => c2.value + 1) // depends on c2
const c2 = computed(() => c1.value + 1) // depends on c1

console.log(c1.value) // Dev warning: "Computed is already running"

Vue prevents infinite recursion but leaves the result undefined. Refactor to eliminate the cycle.

Expensive Computations with Watch

For heavy logic, nested computed properties remain efficient but benefit from explicit observation.

import { ref, computed, watch } from 'vue'

const numbers = ref([1, 2, 3, 4, 5])

const sumOfSquares = computed(() => {
  console.log('recomputing squares')
  return numbers.value.reduce((sum, n) => sum + n * n, 0)
})

const offsetSum = computed(() => sumOfSquares.value + 10)

watch(offsetSum, (newVal) => {
  console.log('Final result:', newVal)
})

numbers.value.push(6) // sumOfSquares recomputes only when offsetSum is accessed

Even with multiple layers, each level recomputes only when its direct dependencies change and the value is actually read.

Summary

  • Nested computed properties are idiomatic in Vue 3 and implemented intentionally in packages/reactivity/src/computed.ts.
  • Dependency tracking is automatic via Dep.track() in packages/reactivity/src/dep.ts, creating efficient subscription links between computed layers.
  • Evaluation is lazy through refreshComputed() in packages/reactivity/src/effect.ts, ensuring getters run only when necessary.
  • Avoid circular dependencies which trigger EffectFlags.RUNNING detection and produce stale results.
  • Keep getters pure to maintain caching guarantees and predictable reactivity behavior.

Frequently Asked Questions

Can I use a computed property inside another computed property in Vue 3?

Yes, this is standard practice. Vue 3's reactivity system explicitly supports this pattern through the ComputedRefImpl class, which tracks dependencies via Dep objects. When an outer computed reads an inner computed's .value, it subscribes to changes automatically.

Does nesting computed properties hurt performance?

No, nesting does not inherently hurt performance because Vue uses lazy evaluation. Computed properties only re-evaluate when accessed after their dependencies change. However, extremely deep chains with expensive synchronous operations can cause cascade delays; in such cases, consider using watch or extracting logic into methods.

How does Vue 3 detect circular dependencies in computed properties?

Vue checks the EffectFlags.RUNNING flag during refreshComputed() in packages/reactivity/src/effect.ts. If a computed property attempts to execute while already running, the system returns early to prevent infinite recursion. Development builds emit a warning when circular dependencies are detected.

Should I use methods instead of nested computed properties?

No, use methods only when the result should not be cached or does not depend on reactive state. Computed properties are optimized for derived state that depends on other reactive values. Nesting them provides better performance through memoization and clearer semantic intent than equivalent method calls.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →