Vue Interview Questions
Common Vue interview questions covering the Composition API, reactivity, directives, routing, and state.
A curated set of Vue.js interview questions, ordered roughly from fundamentals to more advanced — the kind you'll actually be asked in real screens and on-sites.
Core concepts
Q: What's the difference between the Options API and the Composition API?
The Options API organizes a component by kind of code — a data() function, a methods object, a computed object, lifecycle hooks — each in its own section. The Composition API (used via setup() or, more commonly, the <script setup> syntax) instead organizes code by feature, using functions like ref, reactive, computed, and watch that can be freely composed and, importantly, extracted into standalone reusable functions (composables). Both are still fully supported in Vue 3; the Composition API is the recommended default for new projects, especially anything beyond a small component, because it scales better and makes logic reuse far more direct.
Q: What's the difference between ref() and reactive()?
ref() wraps any value — including primitives — in an object with a .value property, and works uniformly everywhere; reactive() makes an object's own properties directly reactive with no wrapper, but only works on objects, not primitives like numbers or strings. A sharp edge with reactive(): destructuring its properties into standalone variables disconnects them from reactivity, since you get a plain copied value rather than a live reference — ref() doesn't have this problem, since you always access it through .value regardless of where it's stored.
Q: What's the difference between v-if and v-show?
v-if conditionally renders an element — when the condition is false, the element (and any component inside it) is fully destroyed and removed from the DOM, and re-created from scratch when the condition becomes true again. v-show always keeps the element in the DOM and merely toggles its CSS display property. Use v-show for something toggled frequently, since it avoids the cost of repeated destroy/recreate cycles; use v-if when the condition rarely changes, or when you specifically want the element's state reset each time it reappears.
Q: Why does Vue want a :key on v-for items, and what's wrong with using the array index?
The key gives Vue a stable identity for each rendered item so it can correctly match old DOM nodes/component instances to new ones when the list changes — reordering, inserting, or removing items. Using the array index as the key breaks this when the list can reorder: an item's index changes even though its identity didn't, so Vue can end up reusing a DOM node (and any local state, like a focused input) for what is now conceptually a different underlying item. A key derived from stable, unique data (a database ID) avoids this.
Composition API
Q: Why extract logic into a composable rather than a mixin (the older Options API pattern for logic reuse)?
Mixins merge their properties into the consuming component implicitly, which makes it hard to trace where a given property or method actually came from, and creates silent naming collisions when two mixins define the same key. A composable is just a plain function you call explicitly (const { count, increment } = useCounter()) and destructure — the source of every value is visible right at the call site, and there's no hidden merging behavior to reason about.
Q: When would you use watch() instead of computed()?
computed() is for deriving a new reactive value from existing ones, purely, with no side effects, and its result is cached until a dependency changes. watch() is for running a side effect — an API call, logging, updating something outside Vue's reactivity system — in response to a specific reactive value changing, and it gives you both the old and new value to compare. If you're returning a value to display, use computed(); if you're reacting to a change to do something, use watch().
Routing and state management
Q: What's the purpose of a navigation guard like router.beforeEach, and how does route meta fit in?
A navigation guard runs before a route change completes and can allow it, redirect elsewhere, or cancel it — the standard place to enforce something like "this route requires an authenticated user." meta is an arbitrary object attached to a route definition (meta: { requiresAuth: true }), commonly used specifically so a single global guard can check to.meta.requiresAuth and decide what to do, without hardcoding a list of protected paths inside the guard itself.
Q: Why reach for Pinia instead of just building a shared composable?
A plain composable creates a fresh, independent instance of its state every time it's called — right for logic like a form's local validation, but wrong for state meant to be one single shared source of truth (a logged-in user, a cart) across unrelated parts of the app. Pinia's defineStore guarantees that calling useSomeStore() anywhere in the app returns the exact same instance, which is what actually makes state "shared" rather than merely "reusable" the way a composable's logic is.
Q: Why does destructuring a Pinia store's state lose reactivity, and what fixes it?
Destructuring copies a store's current property values out into plain, disconnected variables at that moment — the same trap plain reactive() objects have — so a later change to the store's actual state doesn't update those copied values. Pinia's storeToRefs() helper solves this by converting the store's reactive state and getters into individual refs that stay linked to the store, safe to destructure; a store's actions (plain functions) don't have this problem and can be destructured directly.
Testing
Q: Why do Vue Testing Library tests query by role or label text instead of a CSS selector?
Querying the way a real user perceives the page — by visible text, label, or accessibility role — means the test keeps passing through an internal refactor (switching ref for reactive, restructuring a composable) as long as the component's observable behavior is unchanged. A CSS selector or a test-only attribute couples the test to implementation details a real user never interacts with, making it brittle against changes that shouldn't break anything.