Skip to the solution
MediumFrontend11 min read

What is the difference between ref and reactive?

1.1k views

The problem

Question presented to candidate: "In a Vue 3 <script setup> component, when would you use ref and when reactive? Walk me through what each one actually returns and the bugs you have seen from picking the wrong one."

What a strong answer should cover:

  • ref(x) returns a ref object with a tracked .value; it holds any type, and an object placed in it is made deeply reactive.
  • reactive(obj) returns a deep Proxy of the object itself; it only accepts objects (a primitive gets a dev warning and comes back unchanged).
  • A ref can be reassigned as a whole (list.value = [...]); a reactive variable cannot, because effects keep tracking the old proxy.
  • Destructuring a reactive object copies values and loses reactivity; toRefs fixes that. Refs survive being passed around and returned from composables.
  • Unwrapping rules: top-level refs unwrap in templates, refs nested in a reactive object unwrap on access, refs inside reactive arrays and Maps do not.
  • The Vue docs recommend ref() as the primary API; reactive still fits a cohesive object such as a form you never replace.

Clarifying questions expected:

  • "Is this for local component state, or for state shared through a composable or store?"
  • "Is the team on TypeScript? That changes how much .value matters in practice."

Code / implementation expected: Yes β€” show reassignment and destructuring with both, and what the template displays.

  • ref
  • reactive

Try it yourself

Sketch your approach before you read the solution. That is what the interview tests.

Stuck? Get a hint.

A short nudge toward the approach. It will not give the answer away.

Solution

The solution is hidden so you can try first

11 min read Β· 9 sections

Read the code

VueReplacing a ref vs replacing a reactive, and destructuring (click through it)
<script setup>
import { ref, reactive } from "vue";

// ref: a box with .value. Works for any type, and the whole value can be replaced.
const todosRef = ref(["write tests"]);

// reactive: a Proxy around the object itself. Objects only; do not reassign the variable.
let todosReactive = reactive(["write tests"]);

function replaceRef() {
  todosRef.value = ["ship it"]; // the box is the same, its contents changed: tracked
}

function replaceReactive() {
  todosReactive = reactive(["ship it"]); // a brand-new proxy nobody is tracking yet
  console.log("variable now holds:", todosReactive.join());
}

// Destructuring a reactive object copies the current value and loses the link.
const form = reactive({ email: "a@b.co" });
let { email } = form;
function changeEmail() {
  form.email = "new@b.co";
  console.log("form.email:", form.email, "| destructured email:", email);
}
</script>

<template>
  <p id="ref">ref: {{ todosRef.join(", ") }}</p>
  <p id="reactive">reactive: {{ todosReactive.join(", ") }}</p>
  <p id="email">form: {{ form.email }} / destructured: {{ email }}</p>
  <button id="replace-ref" @click="replaceRef">Replace ref</button>
  <button id="replace-reactive" @click="replaceReactive">Replace reactive</button>
  <button id="email-btn" @click="changeEmail">Change email</button>
</template>
Seven ref vs reactive behaviours probed in plain Node
import { ref, reactive, isReactive, isRef, watch, effect } from "vue";

const user = ref({ name: "Ada", tags: ["a"] });
console.log("1. isReactive(user.value):", isReactive(user.value));

const n = reactive(1);
console.log("2. reactive(1) returned:", n, "| isReactive:", isReactive(n));

const listRef = ref([1, 2]);
let listReactive = reactive([1, 2]);
effect(() => console.log("   ref effect sees length", listRef.value.length));
effect(() => console.log("   reactive effect sees length", listReactive.length));
listRef.value = [1, 2, 3];
listReactive = reactive([1, 2, 3]); // the effect still holds the OLD proxy
console.log("3. after reassigning both (only the ref effect re-ran)");

const state = reactive({ count: 0 });
let { count } = state;
state.count = 5;
console.log("4. destructured count:", count, "| state.count:", state.count);

const r = ref(10);
const box = reactive({ r });
const arr = reactive([r]);
console.log("5. box.r:", box.r, "| arr[0] isRef:", isRef(arr[0]), "| arr[0].value:", arr[0].value);
box.r = 11;
console.log("   writing box.r updates the ref too:", r.value);

const raw = { id: 1 };
console.log("6. proxied === raw:", reactive(raw) === raw);

Discussion

Other approaches, follow-ups and how it went in real interviews.

Sign in to join the discussion.

No comments yet. Share how you would answer it.

Question 23 of 92 in Vue.js

Keep your momentum going.

Mark this question solved to track your progress, then take the next one while it is fresh.

All Vue.js questions
Up nextMedium

Pinia colocation in 2025 β€” how do you colocate stores with routes/components?

Continue