Computed vs watch in Vue: derived state (cached) vs side effects (imperative)

MediumIntermediateVue
Preparing for interviews?

Use guided tracks for structured prep, then practice company-specific question sets when you want targeted interview coverage.

Quick Answer

Computed properties and watchers both react to reactive changes, but they solve different problems: computed is for derived state (cached, declarative), while watch is for side effects (async/imperative work triggered by changes).

Answer

Core idea

Computed = a derived value (like a formula). Vue tracks its dependencies and caches the result until one of them changes.
Watch = run code when something changes. It’s meant for side effects (fetching, logging, syncing, timers), not for producing values for the template.

Aspect

computed

watch / watchEffect

Primary purpose

Derived state (calculate a value from other reactive state)

Side effects (do something when state changes)

Caching

Yes (cached until deps change)

No (runs when triggered)

Evaluation model

Lazy: recomputes when accessed after being invalidated

Eager: runs callback when source changes (timing configurable via flush)

Return value

Yes (you read it like a value)

No (callback-driven; produces effects, not a value)

Async work

Avoid (keep it pure)

Yes (common: API calls + cancellation)

Common mistake

Putting side effects in computed

Using watch to keep derived state in sync (duplicate state)

Computed is for values; watch is for effects.

Anti-pattern (common in interviews): using watch for derived state

This duplicates state and can drift out of sync. Prefer computed unless you truly need an effect.

HTML
<!-- BAD: derived state via watch (duplicate state) -->
<script setup>
import { ref, watch, computed } from 'vue';

const first = ref('Mina');
const last = ref('Yilmaz');

const fullNameViaWatch = ref('');
watch([first, last], ([f, l]) => {
  fullNameViaWatch.value = `${f} ${l}`;
}, { immediate: true });

// GOOD: derived state via computed (cached, always in sync)
const fullName = computed(() => `${first.value} ${last.value}`);
</script>

<template>
  <p>watch-derived: {{ fullNameViaWatch }}</p>
  <p>computed: {{ fullName }}</p>
</template>
                  

Watchers (the right use): side effects + async + cancellation

When a value changes and you need to do something (fetch, analytics, sync URL, write to storage), use watch. Use cleanup to avoid race conditions (old request finishing after a new one).

HTML
<script setup>
import { ref, watch } from 'vue';

const query = ref('');
const results = ref([]);
const error = ref(null);

watch(query, async (q, _prev, onCleanup) => {
  if (!q.trim()) {
    results.value = [];
    error.value = null;
    return;
  }

  const ctrl = new AbortController();
  onCleanup(() => ctrl.abort());

  try {
    error.value = null;
    const res = await fetch(`/api/search?q=${encodeURIComponent(q)}`, { signal: ctrl.signal });
    results.value = await res.json();
  } catch (e) {
    // Ignore abort; handle real errors
    if (e?.name !== 'AbortError') error.value = e;
  }
}, { flush: 'post' });
</script>

<template>
  <input v-model="query" placeholder="Search..." />
  <pre v-if="error">{{ error }}</pre>
  <ul>
    <li v-for="r in results" :key="r.id">{{ r.title }}</li>
  </ul>
</template>
                  

Key options you should know (interview-level)


      • immediate: true runs the watcher once on setup (useful for initial fetch).

      • deep: true watches nested mutations (use sparingly; can be expensive). Prefer watching a specific getter like () => obj.a.b.

      • flush: 'pre' (default), 'post' (after DOM updates), 'sync' (runs immediately; use carefully).

      • watchEffect(): tracks dependencies automatically (great for effects that depend on many reactive reads), but use it only for effects (same rule: not for derived display values).

Rule of thumb

If the result is a value you want to render (filtering, formatting, totals) → computed.
If you need to do something when it changes (fetch, sync, log, imperative updates) → watch.

Similar questions
Guides
7 / 34