"How does Vue 3 reactivity work, and why did it switch from defineProperty to Proxy?"
Vue 3 reactivity is the canonical Vue interview question in 2026. Interviewers probe whether you understand the concrete limitations of Vue 2's defineProperty approach and why Proxy is a fundamentally better solution, not just a syntax change.
Start with Vue 2 limits: Object.defineProperty converts each existing property into getter/setter pairs. Problems: can't detect property addition/deletion, can't intercept array index writes or .length changes, must recursively convert every nested property at creation time.
Workarounds in Vue 2: Vue.set / this.$set for new properties, seven patched array methods (push, pop, splice, etc.) that manually trigger updates. Messy and incomplete.
Vue 3's Proxy: wraps the whole object in a single proxy handler that intercepts any operation — get, set, has, deleteProperty, ownKeys. No preprocessing; reactivity handles property addition, deletion, array index writes, and Map/Set mutations natively.
Lazy conversion: Proxy does not recurse into nested objects. Nested objects become reactive only when their getter is first accessed, returning a new reactive Proxy lazily. This dramatically reduces startup cost for large state.
track and trigger are the core functions: inside a getter, track() records that the current effect (computed, watch, render function) depends on this property. Inside a setter, trigger() looks up and re-runs all effects that tracked this property. ref vs reactive: reactive is for objects and returns a Proxy; ref wraps a primitive in an object with a .value property so the Proxy machinery can track access.
Vue 2 implemented reactivity with Object.defineProperty. At component creation, Vue walked every property of the data object and replaced it with a getter/setter pair that called dep.depend() on read and dep.notify() on write. This works but has concrete problems. First, it can't detect adding or deleting a property — this.user.age = 20 when age didn't exist originally does nothing reactive. Second, it can't intercept array index mutations like arr[0] = x or arr.length = 0, so Vue 2 patches seven array methods to manually trigger updates. Third, it has to recursively walk the entire data tree at creation time, which is expensive for large nested state. Vue 3 uses Proxy, a JavaScript feature that wraps an object with a handler intercepting every operation — get, set, has, deleteProperty, ownKeys. You don't pre-convert anything. The Proxy handler's get trap calls track(target, key) to record "the current active effect depends on this property". The set trap calls trigger(target, key) to notify all effects that tracked this property. Property addition, deletion, array index writes, and Map/Set mutations are all captured naturally. Proxies are also lazy — the top-level object is wrapped, and only when you access a nested property does Vue wrap that nested value in another Proxy and return it, so you pay the cost of reactivity only on touched paths. This is huge for large state trees. The two primary APIs: reactive(obj) wraps an object and returns a Proxy. ref(value) is for primitives — since you can't proxy a primitive, ref returns { value: primitive } wrapped in a reactive-like object, so accessing .value is intercepted. In templates, Vue unwraps refs automatically, which is why you don't write .value in JSX / template but do write it in script setup. track() and trigger() are the two function primitives underneath computed, watch, and the render effect — all of them internally are effect()s that register during reads and re-run on writes.
Always compare directly with Vue 2 — the comparison is what interviewers want.
Mentioning lazy deep reactivity is a signal you've read the source code or docs carefully.
Explaining ref's "primitives can't be proxied so we wrap in .value" is a common follow-up.
If you blank on track/trigger, HireMe AI can surface these terms on the fly.
IE11 doesn't support Proxy — Vue 2's IE11 compatibility blocked it. Vue 3 dropped IE11 to enable Proxy.
reactive is for objects; ref is for any value but primarily used for primitives. Refs unwrap automatically in templates but need .value in script code.
Skip the lazy nested wrapping — only the top level is reactive. Useful for large immutable data structures where you replace the whole tree on updates.
HireMe AI listens to your interview in real-time, detects questions, and generates instant answer suggestions — so you never freeze up again.
Try HireMe AI Free