Vue.js Interview Questions
Top 25 Vue.js Interview Questions
Curated questions covering Vue 3 Composition API, Pinia, Vue Router, reactivity, and component patterns.
What is the Composition API in Vue 3 and why was it introduced?
The Composition API is a set of additive, function-based APIs that allow flexible composition of component logic. It was introduced to solve limitations of the Options API: poor TypeScript support, difficulty reusing logic across components, and large components becoming hard to read when related logic was split across different options.
import { ref, computed, onMounted } from "vue";
export default {
setup() {
const count = ref(0);
const doubled = computed(() => count.value * 2);
onMounted(() => console.log("mounted"));
return { count, doubled };
}
};
What is the difference between the Options API and the Composition API?
- Options API — organises code by option type (data, methods, computed, watch). Easier for beginners; less flexible for large components.
- Composition API — organises code by logical concern using setup(). Better TypeScript support, easier logic reuse via composables.
- Both APIs are fully supported in Vue 3; they can even be mixed in the same component.
What is the difference between ref() and reactive() in Vue 3?
ref() wraps a single value (primitive or object) in a reactive reference accessed via .value. reactive() makes an entire object deeply reactive without needing .value. Use ref() for primitives and when you need to reassign the whole value; use reactive() for complex objects.
import { ref, reactive } from "vue";
const count = ref(0);
count.value++; // must use .value
const state = reactive({ name: "Alice", age: 25 });
state.age++; // no .value needed
How does computed() work in Vue 3?
computed() creates a reactive reference whose value is derived from other reactive sources. It is cached — it only re-evaluates when its reactive dependencies change, making it more efficient than a method called in the template.
import { ref, computed } from "vue";
const firstName = ref("John");
const lastName = ref("Doe");
const fullName = computed(() => `${firstName.value} ${lastName.value}`);
console.log(fullName.value); // "John Doe"
What is the difference between watch() and watchEffect()?
- watch() — explicitly watches one or more reactive sources; receives old and new values; lazy by default (does not run on initial render).
- watchEffect() — automatically tracks all reactive dependencies used inside it; runs immediately on creation; no access to old value.
- Use watch() when you need the previous value or want lazy execution; use watchEffect() for side effects that should run whenever dependencies change.
import { ref, watch, watchEffect } from "vue";
const count = ref(0);
watch(count, (newVal, oldVal) => {
console.log(`${oldVal} -> ${newVal}`);
});
watchEffect(() => {
console.log("count is:", count.value); // runs immediately
});
What are Vue 3 lifecycle hooks in the Composition API?
- onBeforeMount / onMounted — before/after the component is inserted into the DOM.
- onBeforeUpdate / onUpdated — before/after a reactive change triggers a DOM update.
- onBeforeUnmount / onUnmounted — before/after the component is removed from the DOM.
- onErrorCaptured — called when an error from a descendant component is captured.
How does v-model work in Vue 3?
v-model creates a two-way binding. On native inputs it binds :value and @input. On components it binds :modelValue and emits update:modelValue. Vue 3 supports multiple v-model bindings on a single component.
<!-- Parent -->
<MyInput v-model="username" />
<!-- MyInput component -->
<script setup>
const props = defineProps(["modelValue"]);
const emit = defineEmits(["update:modelValue"]);
</script>
<template>
<input :value="props.modelValue" @input="emit('update:modelValue', $event.target.value)" />
</template>
What is the difference between v-bind and v-on?
- v-bind (:) — dynamically binds an attribute or prop to an expression. Shorthand: :attr="value".
- v-on (@) — attaches an event listener to an element or component. Shorthand: @event="handler".
<img :src="imageUrl" :alt="imageAlt" />
<button @click="handleClick" @keyup.enter="handleEnter">Click</button>
How do props and emits work in Vue 3?
Props pass data from parent to child (one-way data flow). Emits allow the child to send events back to the parent. In the Composition API with <script setup>, use defineProps() and defineEmits().
// Child.vue
<script setup>
const props = defineProps({ title: String, count: Number });
const emit = defineEmits(["increment"]);
</script>
<template>
<button @click="emit('increment')">{{ props.title }}: {{ props.count }}</button>
</template>
What are slots in Vue and what are scoped slots?
Slots allow a parent component to inject content into a child component's template. Named slots allow multiple injection points. Scoped slots expose child data back to the parent's slot content.
<!-- Child: Card.vue -->
<template>
<div class="card">
<slot name="header" />
<slot :item="item" /> <!-- scoped slot -->
</div>
</template>
<!-- Parent -->
<Card>
<template #header><h2>Title</h2></template>
<template #default="{ item }">{{ item.name }}</template>
</Card>
What is provide/inject in Vue 3?
provide/inject is a dependency injection mechanism for passing data deeply through the component tree without prop drilling. A parent provides a value; any descendant can inject it.
// Ancestor
import { provide, ref } from "vue";
const theme = ref("dark");
provide("theme", theme);
// Descendant
import { inject } from "vue";
const theme = inject("theme", "light"); // "light" is default
What is Pinia and how does it differ from Vuex?
- Pinia is the official state management library for Vue 3. It is simpler, fully TypeScript-friendly, and has no mutations — only state, getters, and actions.
- Vuex requires mutations for synchronous state changes and actions for async; Pinia merges both into actions.
- Pinia supports multiple stores without nesting; Vuex uses a single store with modules.
import { defineStore } from "pinia";
export const useCounterStore = defineStore("counter", {
state: () => ({ count: 0 }),
getters: { doubled: (s) => s.count * 2 },
actions: {
increment() { this.count++; }
}
});
How does Vue Router work in Vue 3?
Vue Router maps URL paths to components. It supports history and hash modes, nested routes, route guards (beforeEach, beforeEnter), dynamic segments, and lazy-loaded routes.
import { createRouter, createWebHistory } from "vue-router";
const router = createRouter({
history: createWebHistory(),
routes: [
{ path: "/", component: () => import("./Home.vue") },
{ path: "/user/:id", component: () => import("./User.vue") },
]
});
What is the defineProps() and defineEmits() macro in <script setup>?
defineProps() and defineEmits() are compiler macros available only inside <script setup>. They declare the component's props and emitted events with full TypeScript support and no need to import them.
<script setup lang="ts">
const props = defineProps<{ title: string; count?: number }>();
const emit = defineEmits<{ (e: "update", val: number): void }>();
</script>
What is Teleport in Vue 3?
Teleport renders a component's template in a different part of the DOM (outside the component tree), while keeping the component's logic in place. Useful for modals, tooltips, and overlays.
<template>
<Teleport to="body">
<div class="modal" v-if="isOpen">
<p>Modal content rendered directly in <body></p>
</div>
</Teleport>
</template>
What is Suspense in Vue 3?
Suspense is a built-in component that handles async dependencies in a component tree. It shows a fallback slot while async setup() functions or async components are resolving.
<Suspense>
<template #default>
<AsyncComponent />
</template>
<template #fallback>
<p>Loading...</p>
</template>
</Suspense>
How do you create a custom directive in Vue 3?
Custom directives are registered with app.directive() or locally in a component. They expose lifecycle hooks: created, beforeMount, mounted, beforeUpdate, updated, beforeUnmount, unmounted.
// Global directive: v-focus
app.directive("focus", {
mounted(el) { el.focus(); }
});
// Usage
<input v-focus />
What is the difference between mixins and composables?
- Mixins — merge options into a component; can cause naming conflicts and unclear data origins. Legacy pattern.
- Composables — plain functions using the Composition API that return reactive state and methods. Explicit, type-safe, and conflict-free.
- Composables are the recommended replacement for mixins in Vue 3.
// useCounter.js (composable)
import { ref } from "vue";
export function useCounter(initial = 0) {
const count = ref(initial);
const increment = () => count.value++;
return { count, increment };
}
What are template refs in Vue 3?
Template refs give direct access to a DOM element or child component instance. In the Composition API, declare a ref with the same name as the ref attribute.
<script setup>
import { ref, onMounted } from "vue";
const inputEl = ref(null);
onMounted(() => inputEl.value.focus());
</script>
<template>
<input ref="inputEl" />
</template>
What is the v-if vs v-show difference in Vue?
- v-if — conditionally renders the element; the element is added/removed from the DOM. Higher toggle cost, lower initial render cost when false.
- v-show — always renders the element but toggles CSS display:none. Lower toggle cost, higher initial render cost.
- Use v-if for infrequently toggled content; v-show for frequently toggled content.
What is the key attribute used for in Vue lists?
The key attribute gives Vue a hint to identify each node in a v-for list. It enables efficient DOM patching by reusing and reordering existing elements rather than re-rendering the entire list.
<ul>
<li v-for="item in items" :key="item.id">
{{ item.name }}
</li>
</ul>
What is the difference between shallow ref and shallowReactive?
shallowRef() only makes the top-level .value reactive — nested properties are not tracked. shallowReactive() only makes the top-level properties of an object reactive. Both are performance optimisations for large data structures where deep reactivity is unnecessary.
How does Vue 3 handle component communication patterns?
- Parent → Child: props
- Child → Parent: emits
- Sibling / distant: Pinia store or provide/inject
- Any component: event bus (mitt library) or global state
What is the <script setup> syntax in Vue 3?
<script setup> is syntactic sugar for the Composition API. Top-level bindings (variables, functions, imports) are automatically exposed to the template. It results in less boilerplate and better runtime performance.
<script setup>
import { ref } from "vue";
const message = ref("Hello Vue 3!");
function greet() { alert(message.value); }
</script>
<template>
<button @click="greet">{{ message }}</button>
</template>
What is the Vue 3 reactivity system based on?
Vue 3's reactivity system is built on ES6 Proxies (replacing Vue 2's Object.defineProperty). Proxies intercept get/set operations on objects, enabling automatic dependency tracking and triggering updates when reactive data changes. This also allows detecting property additions and deletions that Vue 2 could not track.