Vue.js Interview Questions and Answers (2026)
Vue.js interview questions often focus on the reactivity system and the shift from the Options API to the Composition API. Here are the Vue questions that come up most often, with answers that explain the mechanics, not just the syntax.
-
How does Vue's reactivity system differ between Vue 2 and Vue 3?
Vue 2 achieves reactivity by walking a data object at initialization and converting each property to a getter/setter pair via
Object.defineProperty, which tracks dependencies on read and triggers updates on write. This has real limitations: it can't detect new properties added after initialization, or array index/length mutations, without workarounds likeVue.set. Vue 3 rewrote reactivity using ES6Proxy, which intercepts operations on the *whole* object (get, set, delete, has) rather than per-property, so newly added properties, array mutations, andMap/Setare all reactive without special-casing.// Vue 3 (Proxy-based) const state = reactive({ count: 0 }); state.newProp = 'works!'; // reactive automaticallyProxy also enables lazy, more efficient dependency tracking since it doesn't need to eagerly walk the whole object tree upfront.
-
Composition API vs Options API — what's the actual tradeoff?
The Options API organizes a component by option type (
data,methods,computed, lifecycle hooks) — easy to learn and predictable for small components, but logic for one feature gets scattered across multiple options as components grow. The Composition API (setup()or<script setup>) organizes code by logical concern instead, letting you group all state, computed values, and effects for one feature together, and extract that entire group into a reusable composable function — Vue's equivalent of React custom hooks.// Composition API import { ref, computed } from 'vue'; export function useCounter() { const count = ref(0); const doubled = computed(() => count.value * 2); const increment = () => count.value++; return { count, doubled, increment }; }Options API remains fine for simple components; Composition API wins for reusable logic, TypeScript inference, and larger components with many concerns.
-
Computed properties vs watchers — when should you use each?
Computed properties are derived, cached values — declaratively defined in terms of reactive dependencies, recomputed only when a dependency changes, and re-used from cache otherwise. They should be pure and side-effect-free. Watchers are for reacting imperatively to a change — running side effects like an API call, logging, or manually updating unrelated state when a specific reactive source changes.
const firstName = ref('Jane'); const lastName = ref('Doe'); const fullName = computed(() => `${firstName.value} ${lastName.value}`); // derived watch(firstName, (newVal) => { console.log('First name changed to', newVal); // side effect });The common anti-pattern is using a watcher to just recompute another piece of state that could instead be a
computed— this adds unnecessary indirection and bugs from manual sync. Usecomputedfor "this value depends on that," watchers for "when that changes, do something." -
What does `v-model` do under the hood?
v-modelis syntactic sugar for binding a value and listening for its update event in one directive — on a native input it expands to:value+@input(orchecked/changefor checkboxes/radios). On a component, it defaults to amodelValueprop plus anupdate:modelValueemit (Vue 3), which the component must implement to support two-way binding.<!-- Usage --> <CustomInput v-model="search" /> <!-- Inside CustomInput --> <input :value="modelValue" @input="$emit('update:modelValue', $event.target.value)">Vue 3 also supports multiple
v-modelbindings on one component using named arguments (v-model:title="title"), which wasn't possible in Vue 2 where only onev-modelper component was allowed. -
How do components communicate — explain props/emit and provide/inject.
Props flow data down from parent to child (one-way); the child should never mutate a prop directly, instead emitting a custom event for the parent to act on — this keeps data flow predictable and traceable ("props down, events up").
<!-- Parent --> <Child :count="count" @increment="count++" /> <!-- Child --> defineProps(['count']); const emit = defineEmits(['increment']);For deeply nested trees where prop drilling through many intermediate components becomes unwieldy,
provide/injectlets an ancestor expose a value that any descendant can pull directly, regardless of depth — similar to React Context. It's best reserved for cross-cutting concerns (theme, current user, i18n) rather than as a general substitute for props, since it makes data flow harder to trace. -
`v-if` vs `v-show` — what's the difference and when do you pick each?
v-ifconditionally renders an element — when false, the element and its component instance (with all its lifecycle hooks, state, and child components) are fully destroyed and removed from the DOM; toggling it re-triggers mount/unmount lifecycle hooks and is relatively expensive per toggle.v-showalways renders the element but toggles CSSdisplay: none, keeping the component instance and DOM node alive — cheap to toggle repeatedly but pays the initial render cost even if never shown.<ExpensiveWidget v-if="isAdmin" /> <!-- not rendered at all if false --> <Tooltip v-show="isHovering" /> <!-- always in DOM, just hidden -->Rule of thumb:
v-showfor elements that toggle frequently (better runtime perf),v-iffor conditions that rarely change or that gate content that's expensive/unsafe to even instantiate. -
What is a Single File Component and what are its three sections?
A
.vueSingle File Component bundles a component's template, logic, and styles into one file with three top-level blocks:<template>(the HTML-like markup, compiled to a render function),<script>/<script setup>(the component logic — state, computed, methods), and<style>(CSS, optionallyscopedto auto-generate unique attribute selectors so styles don't leak to other components).<template><button @click="count++">{{ count }}</button></template> <script setup> import { ref } from 'vue'; const count = ref(0); </script> <style scoped> button { color: blue; } </style>This colocation improves maintainability by keeping everything relevant to one component together, and the build tooling (Vite/vue-loader) compiles each block appropriately — template to a render function, style to CSS with scoping applied via generated data attributes.
-
Vuex vs Pinia — why did the ecosystem move to Pinia?
Vuex (Vue 2/3 era) organizes global state into modules with
state,getters,mutations(the only way to synchronously change state, for devtools traceability), andactions(for async logic that commits mutations) — functional but verbose, with weak TypeScript inference and boilerplate for even simple stores. Pinia, now the official recommendation, drops the mutations layer entirely (actions can mutate state directly), has first-class TypeScript support with full autocompletion, works naturally with the Composition API, and supports multiple independent stores without a single monolithic module tree.export const useCounterStore = defineStore('counter', () => { const count = ref(0); const increment = () => count.value++; return { count, increment }; });Pinia stores are also inherently modular and tree-shakeable, and devtools/hot-module-replacement support is comparable to Vuex without the extra ceremony.
-
What are the key lifecycle hooks in the Composition API?
Composition API lifecycle hooks are imported functions called inside
setup(), mirroring Options API hooks but prefixed withon:onMounted(DOM is ready — good for API calls needing element refs or third-party library init),onUpdated(after a reactive-triggered DOM update),onUnmounted(cleanup — remove listeners, cancel timers/subscriptions),onBeforeMount/onBeforeUpdate/onBeforeUnmountfor their respective "about to happen" moments.import { onMounted, onUnmounted } from 'vue'; onMounted(() => { window.addEventListener('resize', handleResize); }); onUnmounted(() => { window.removeEventListener('resize', handleResize); });Unlike Options API where hooks are object properties (
mounted() {}), Composition API hooks can be called multiple times within the samesetup()(e.g., inside a composable used twice), and each registers an independent callback — useful for encapsulating lifecycle-bound logic inside reusable composables. -
How do slots work, and what's the difference between default, named, and scoped slots?
Slots let a parent inject template content into designated spots inside a child component, enabling flexible, composable UI (similar to React's
childrenbut more powerful). A default slot is unnamed content placed wherever<slot>appears. Named slots (<slot name="header">/<template #header>) let a parent target multiple distinct insertion points. Scoped slots let the child pass data back up to the parent's slot content, inverting the usual one-way data flow for that specific render.<!-- Child --> <slot name="item" :data="row"></slot> <!-- Parent --> <Table> <template #item="{ data }">{{ data.name }}</template> </Table>Scoped slots are the mechanism behind renderless/headless components — the child owns the logic and iteration, the parent owns the presentation.
-
How do Vue Router navigation guards work?
Navigation guards let you intercept route transitions to control access or perform work before/after navigation completes, at three levels: global (
router.beforeEach, runs for every navigation — typical for auth checks), per-route (beforeEnter, defined on a specific route config), and in-component (beforeRouteEnter/beforeRouteUpdate/beforeRouteLeave, defined inside the component itself — useful for "unsaved changes" prompts).router.beforeEach((to, from) => { if (to.meta.requiresAuth && !isLoggedIn()) { return { name: 'login' }; } });Guards can return
falseto cancel navigation, a route location to redirect, or nothing/trueto proceed; they can also be async, which is essential for guards that need to check auth state from an API before deciding. -
What are the key breaking/architectural differences between Vue 2 and Vue 3 beyond reactivity?
Beyond the Proxy-based reactivity rewrite, major differences include: the Composition API as a new (optional) way to organize logic; support for multiple root nodes in a template (Vue 2 required exactly one root element via a single root
<div>); a rewritten, faster virtual DOM with compiler-informed patch flags for better runtime performance; Teleport for rendering a component's content into a different part of the DOM tree (e.g., modals attached tobody); Suspense for coordinating async component loading; and the removal of filters and some Vue 2-only APIs ($on/$off/$oncefor event buses).<template> <header>...</header> <main>...</main> <!-- multiple roots, valid in Vue 3 --> </template>TypeScript support is also far more robust in Vue 3, since the core was rewritten in TypeScript from the ground up rather than retrofitted.
Practicing for a real interview? Get live AI help with your answers, in the browser, free to start.
Start a free session