Vue – Interview Questions

December 29, 20258 min readUpdated 8/24/2026

The questions Vue interviews actually ask, answered the way you would say them out loud. Each one links back to the lesson that covers it properly.

Reactivity

What is the difference between ref and reactive?

ref wraps any value, including primitives, and you access it through .value. reactive takes an object and returns a proxy with no .value.

The real answer is when to use which, and it is ref for almost everything. reactive has three failure modes that are all silent: it does nothing on a primitive, destructuring it produces a disconnected plain value, and reassigning the variable throws the reactivity away. ref has one visible cost — typing .value — and no silent failures. Vue's own docs now recommend it as the default. Lesson 5.

Why does a template not need .value?

Templates are compiled, so the compiler knows which bindings are refs and unwraps them. In script there is no compiler involved, so you unwrap it yourself.

How does Vue know what to re-render?

Dependency tracking. While a component renders, every reactive property it reads registers that render as a subscriber via a proxy's get trap. Writing to that property notifies the subscribers, and only those components re-render.

The follow-up worth having ready: this is why Vue needs no dependency array. React re-runs a component function and compares; Vue records what was actually read. It is also why reactivity stops working after an await — the synchronous execution context that was collecting reads has ended.

Vue 2 used Object.defineProperty. What changed?

Vue 3 uses Proxy. defineProperty had to walk an object at creation and install a getter and setter per key, so it could not see a property added later, a property deleted, or an array index assigned — hence Vue.set and Vue.delete. A proxy intercepts the whole object, so all of that just works and both helpers are gone.

Computed and watch

computed versus method?

A computed caches and only recalculates when a dependency changes. A method runs on every render. If a template reads a value, make it a computed; use a method for something the template calls with arguments. Lesson 6.

computed versus watch?

The clean line: a computed derives a value and must be pure. A watcher runs a side effect — fetch, play, store, start a timer.

The most common mistake is a watcher that only assigns to another ref. That is a computed with extra steps, an extra piece of state that can drift, and no caching. Lesson 7.

Why does my watcher never fire?

Almost always because the source is not reactive. watch(route.query, ...) passes the current object; watch(() => route.query, ...) passes a getter Vue can re-run. Anything that is not a ref needs the getter form, and getting it wrong fails silently.

Components

What does <script setup> compile to?

The block's body becomes the component's setup() function, and every top-level binding is exposed to the template automatically — no return statement, no components option.

It is also faster: the compiler knows the bindings statically, so it references them directly in the generated render function instead of going through an instance proxy. defineProps, defineEmits and defineModel are compiler macros, not imports, which is why they only work at the top level. Lesson 3.

How do components communicate?

Props down, events up, for a parent and child. provide/inject for a value belonging to a subtree, skipping the components in between. A Pinia store for state belonging to the application. Slots when the parent is deciding what something looks like rather than what it says.

Why can't I assign to a prop?

The parent owns the value, and a re-render from the parent would overwrite anything the child wrote. Copy it into a local ref if the child should own it, use a computed if it is derived, or emit an event and let the parent decide. Lesson 10.

What does v-model desugar to?

On an input, :value plus @input. On a component, a modelValue prop plus an update:modelValue event — which is why a component supports it by declaring exactly those two. defineModel (3.4+) collapses both into one line. Lesson 12.

When is a slot better than a prop?

When the parent is deciding what it looks like rather than what it says. The smell that you chose wrong is a family of props all describing one element — actionLabel, actionIcon, actionVariant. That is a slot that has not been written yet. Lesson 13.

Lists and keys

Why does :key matter?

It tells Vue which new item corresponds to which existing DOM element, so it can reuse rather than rebuild.

Use a stable unique id, not the index. With index keys, deleting the first of three items makes the second become index 0, so Vue treats it as an update rather than a removal — and anything living in the DOM rather than in your data (typed text, focus, video position, a transition mid-flight) stays with the element and attaches to the wrong item. Lesson 8.

v-if versus v-show?

v-if adds and removes the element; v-show always renders it and toggles display. v-if is cheaper to leave off, v-show is cheaper to toggle. Default to v-if; use v-show for something that toggles often and is not expensive to keep mounted.

Why not v-if and v-for on the same element?

In Vue 3, v-if has the higher priority, so it is evaluated before the loop variable exists. Filter in a computed instead — which also caches, so it is faster anyway.

State and structure

Pinia versus Vuex?

Pinia is the official recommendation now. No mutations — an action changes state directly. No modules — you define several stores. Much better TypeScript inference, and a smaller runtime. If a tutorial mentions commit, it predates this. Lesson 17.

Why does destructuring a store break it?

The store is a reactive object; destructuring copies out plain values that are no longer connected to it. storeToRefs converts state and getters to refs that stay linked. It deliberately does not convert actions, because a plain function does not need it.

Pinia or provide/inject?

Does the value belong to the application or to a subtree? The signed-in user is application state — one exists. A tabs component's selected tab belongs to that instance, because a page might have three sets of tabs. Reach for a store first. Lesson 16.

What is a composable, and how is it better than a mixin?

A function using Vue's reactivity and lifecycle APIs, named useX, called from setup.

Mixins merged properties in from a distance: two could silently overwrite each other, nothing told you where a property came from, and a component's real surface was unknowable. A composable is a function call with an explicit return value, so the source of every binding is visible at the call site.

The other half of the answer, which shows you have written one: a composable owns its cleanup, so a caller cannot start something without stopping it. Lesson 15.

Practical

Why do my routes 404 after a refresh in production?

History mode uses pushState, so in-app navigation makes no request. A refresh does, for a path that is not a real file. The server needs a rewrite serving index.html with a 200 for anything that is not a file — try_files $uri $uri/ /index.html in nginx. Lesson 27.

How do you make the initial bundle smaller?

Route-level code splitting: component: () => import("..."). Lazy-load every route except the landing one, where a lazy chunk just adds a round trip before the first pixel. Then defineAsyncComponent for anything heavy behind a condition. Lesson 24.

When do you reach for v-memo or shallowRef?

The answer interviewers want is "after measuring". shallowRef is for a large object you replace rather than edit. v-memo is for large lists and is easy to get wrong — omit a value the subtree reads and it renders stale data silently. Structural fixes usually beat both: split the component so it depends on less. Lesson 25.

How does Teleport help a modal?

It moves the rendered DOM elsewhere — usually <body> — while keeping the element in the component tree, so state, handlers and scoped styles are unaffected.

The reason it is needed: position: fixed is not immune to ancestors. A transform, filter or contain on any ancestor makes it the containing block, and the overlay is clipped to it. Lesson 23.

The big one

Composition API or Options API?

This is really asking whether you have shipped Vue or read about it. A good answer has three parts.

The Composition API for new code, and the main reason is logic reuse. Mixins were the Options API's answer and they merge from a distance. Composables are function calls. Everything else — better TypeScript inference, related code staying together instead of being scattered across data, computed, methods and mounted, less shipped code — follows from that.

The Options API is not deprecated and is not going away. It is what every Vue 2 codebase uses, and its imposed structure is genuinely easier for a small component and for someone new.

Consistency beats either. A codebase half in each is worse than a codebase entirely in the one you like less. When you meet an Options component, read it — do not convert it. A working component has no bug that rewriting fixes, and the rewrite is a chance to add one. Lesson 18.

Questions to ask them

An interview goes both ways, and these tell you a lot:

Composition or Options, and is the codebase consistent? "Both, depending who wrote it" is an honest answer and tells you what maintenance feels like.

Vue 2 or 3, and if 2, what is the migration plan? Vue 2 reached end of life at the end of 2023.

What does the test suite look like? "We rely on manual QA" is information.

SPA or Nuxt, and why? Whether they made that decision deliberately says a lot about how the frontend is run.

That is the track. Back to the index.