# Vue watchEffect vs watch: Understanding the Difference in Vue 3 Composition API

> Discover the difference between Vue watchEffect and watch in Vue 3 Composition API. Learn when to use each for optimal reactivity and performance.

- Repository: [Vue/core](https://github.com/vuejs/core)
- Tags: deep-dive
- Published: 2026-02-20

---

**`watchEffect` runs immediately and tracks any reactive dependencies accessed inside its callback automatically, while `watch` requires explicit source declaration and provides access to previous and new values.**

When working with the Vue 3 Composition API in the `vuejs/core` repository, understanding the distinction between `watchEffect` and `watch` is essential for building reactive applications. Both functions reside in the reactivity system but serve fundamentally different purposes regarding dependency tracking, callback signatures, and execution timing.

## Core Differences Between watchEffect and watch

The primary distinction lies in how each function tracks reactive dependencies and what information they provide to your callbacks.

| Aspect | `watchEffect` | `watch` |
|--------|---------------|---------|
| **Signature** | `watchEffect(effect, options?)` | `watch(source, callback?, options?)` |
| **Dependency Tracking** | Implicitly tracks any reactive values accessed inside the effect function | Explicitly watches declared sources (`ref`, `computed`, getter, or array) |
| **Callback Arguments** | Receives only `onCleanup` function | Receives `(newValue, oldValue, onCleanup)` |
| **Options** | Limited to `flush` (`'pre' \| 'post' \| 'sync'`) | Full `WatchOptions` including `immediate`, `deep`, `once`, `flush` |
| **Execution** | Runs immediately and re-runs when any accessed dependency changes | Runs only when specified source changes (or immediately with `immediate: true`) |

### Signature and Source Tracking

In [`packages/runtime-core/src/apiWatch.ts`](https://github.com/vuejs/core/blob/main/packages/runtime-core/src/apiWatch.ts), `watchEffect` delegates to the internal `doWatch` function with a `null` source parameter, indicating it should operate as a "simple effect" without explicit source tracking【`watchEffect` implementation†https://github.com/vuejs/core/blob/main/packages/runtime-core/src/apiWatch.ts#L55-L61】.

Conversely, `watch` requires you to provide a `source`—whether a reactive object, ref, computed property, or getter function. This explicit declaration allows the watcher to precisely track only the specified dependencies rather than any reactive value touched during execution.

### Callback Arguments and Options

`watchEffect` provides minimal context—only an `onCleanup` function for registering cleanup logic before re-execution. The `watch` function, however, supplies both `newValue` and `oldValue` to your callback, enabling comparison logic, animations, or conditional updates based on value transitions.

The options diverge significantly as well. While `watchEffect` only accepts `flush` timing options, `watch` supports `immediate` (run on initialization), `deep` (recursive watching of nested objects), and `once` (run only once when the source changes)【`WatchOptions`†https://github.com/vuejs/core/blob/main/packages/runtime-core/src/apiWatch.ts#L49-L53】.

## How watchEffect Works Under the Hood

The implementation in [`packages/reactivity/src/watch.ts`](https://github.com/vuejs/core/blob/main/packages/reactivity/src/watch.ts) handles the core logic for both functions. When you call `watchEffect`, the system creates a `ReactiveEffect` that executes your provided function immediately. Because no source is specified, the effect tracks every reactive dependency accessed during execution automatically.

```typescript
import { ref, watchEffect } from 'vue'

const count = ref(0)
const multiplier = ref(2)

watchEffect((onCleanup) => {
  // Automatically tracks both count and multiplier
  console.log('Result:', count.value * multiplier.value)
  
  // Setup cleanup for previous runs
  const controller = new AbortController()
  onCleanup(() => controller.abort())
  
  // Fetch data using controller.signal...
})

// Changing either value triggers the effect
count.value = 5
multiplier.value = 3

```

This "autorun" style behavior makes `watchEffect` ideal for side effects that simply need to stay synchronized with reactive state without caring about which specific property changed or what the previous value was.

## How watch Works Under the Hood

The `watch` function provides granular control by requiring explicit source declaration. In [`packages/reactivity/src/watch.ts`](https://github.com/vuejs/core/blob/main/packages/reactivity/src/watch.ts), the implementation resolves your source into a getter function, then compares new and old values when the reactive effect triggers【`watch` implementation†https://github.com/vuejs/core/blob/main/packages/reactivity/src/watch.ts#L20-L30】.

### Watching a Single Source

```typescript
import { ref, watch } from 'vue'

const name = ref('Alice')

watch(name, (newVal, oldVal) => {
  console.log(`Name changed from ${oldVal} to ${newVal}`)
}, { immediate: true })  // Runs immediately with undefined as oldVal

```

### Deep Watching Complex Objects

When watching reactive objects or nested properties, use the `deep` option to traverse nested dependencies:

```typescript
import { reactive, watch } from 'vue'

const settings = reactive({ 
  theme: { dark: false },
  notifications: { email: true }
})

watch(
  () => settings.theme,
  (newVal, oldVal) => {
    console.log('Theme updated:', newVal)
  },
  { deep: true }  // Watches nested properties inside theme
)

```

### Watching Multiple Sources

```typescript
import { ref, watch } from 'vue'

const firstName = ref('John')
const lastName = ref('Doe')

watch([firstName, lastName], ([newFirst, newLast], [oldFirst, oldLast]) => {
  console.log(`Name changed: ${oldFirst} ${oldLast} → ${newFirst} ${newLast}`)
})

```

## When to Use watchEffect vs watch

Choosing between these functions depends on your specific requirements for dependency tracking and callback information.

**Use `watchEffect` when:**
- You need a "fire and forget" side effect that automatically tracks all reactive dependencies
- You don't need access to the previous value
- You're performing simple synchronization tasks like updating the DOM, logging, or fetching data based on multiple reactive values
- You want the effect to run immediately without waiting for the first change

**Use `watch` when:**
- You need to watch specific sources rather than any accessed reactive value
- You require access to `oldValue` and `newValue` for comparison or transition logic
- You need advanced options like `deep` (recursive watching), `immediate` (run on mount), or `once` (single execution)
- You want to avoid unnecessary re-runs when unrelated reactive state changes within the same scope

## Summary

- **`watchEffect`** provides automatic dependency tracking by running an effect function immediately and re-running whenever any reactive value accessed inside changes, but offers no access to previous values.
- **`watch`** requires explicit source declaration and provides `(newValue, oldValue)` to the callback, supporting advanced options like `deep`, `immediate`, and `once` for granular control over reactive observation.
- Both functions delegate to the internal `doWatch` implementation in [`packages/runtime-core/src/apiWatch.ts`](https://github.com/vuejs/core/blob/main/packages/runtime-core/src/apiWatch.ts) and [`packages/reactivity/src/watch.ts`](https://github.com/vuejs/core/blob/main/packages/reactivity/src/watch.ts), sharing the same underlying `ReactiveEffect` architecture but differing in source resolution and callback invocation patterns.

## Frequently Asked Questions

### Can I access the previous value inside watchEffect?

No, `watchEffect` does not provide access to previous values. The callback receives only an `onCleanup` function for registering cleanup logic. If you need to compare new and old values, use `watch` instead, which passes `(newValue, oldValue)` as arguments to your callback.

### Why does watchEffect run immediately while watch waits for changes?

By design, `watchEffect` executes immediately to establish its reactive dependencies and perform initial side effects. In contrast, `watch` only runs when its explicitly declared source changes, unless you specify `immediate: true` in the options object. This behavior difference reflects their distinct use cases: `watchEffect` for immediate synchronization and `watch` for change-specific reactions.

### When should I use flush: 'post' with these watchers?

Use `flush: 'post'` when your watcher needs to access the DOM after Vue has updated it, or when you want to defer the watcher callback until after the component has rendered. This is particularly useful in `watchEffect` when performing DOM measurements or animations that depend on the final rendered state. Without this option, both `watch` and `watchEffect` run in the 'pre' flush mode by default, executing before the component updates.

### Can I stop a watcher manually?

Yes, both `watch` and `watchEffect` return a stop function that you can call to terminate the watcher and clean up its dependencies. For example: `const stop = watchEffect(() => { ... })` followed by `stop()` when you no longer need the effect. This is essential for preventing memory leaks in dynamic components or when watchers are created conditionally.