💚

Top 51 Vue.js Interview Questions & Answers (2026)

51 Questions 31 Beginner 14 Intermediate 6 Advanced

About Vue.js

Top 100 Vue.js interview questions covering components, directives, Composition API, Vuex, Pinia, Vue Router, reactivity system, performance optimization, and Vue 3 features. Companies hiring for Vue.js roles test this knowledge at every stage — from first-round screens that check the basics to final-round conversations about real-world trade-offs.

What to Expect in a Vue.js Interview

Expect a mix of conceptual and practical Vue.js questions: clear definitions and core-concept checks for junior roles, hands-on scenario questions for mid-level roles, and architecture or trade-off discussions for senior roles. Interviewers usually move from foundational topics toward the kind of problems you'd actually face on the job.

How to Use This Guide

Work through the Vue.js questions in order — Beginner, then Intermediate, then Advanced — so each concept builds on the last. Every question has its own page; bookmark the ones that trip you up and revisit them the day before your interview.

Curated by Tech Baithak Editorial Team  ·  Last updated: May 2026

Beginner 31 questions

Core concepts every Vue.js developer must know.

01

What is Vue.js?

Vue.js is a progressive, open-source JavaScript framework for building user interfaces and single-page applications. Created by Evan You in 2014, it was designed to be incrementally adoptable — you can use it to enhance a small part of an existing page or build a full-featured SPA. Key characteristics: (1) Progressive framework: the core library focuses only on the view layer. You can use just Vue for a widget, or combine it with Vue Router and Pinia for a full framework; (2) Component-based: UIs are composed of reusable, self-contained components; (3) Reactive data binding: Vue automatically tracks data dependencies and re-renders components when data changes; (4) Approachable: easy to learn — plain JavaScript with HTML templates. Shorter learning curve than Angular, more opinionated than React; (5) Versatile: suitable for small widgets, SPAs, and full-stack apps with Nuxt.js. Vue 3 (released 2020) introduced the Composition API, improved TypeScript support, better performance (Proxy-based reactivity), and a smaller bundle size. Used by Alibaba, Xiaomi, Adobe, GitLab, and many others.

Open this question on its own page
02

What is the difference between Vue 2 and Vue 3?

Vue 3 is a major rewrite with significant improvements: Composition API: Vue 3 introduced the Composition API (setup() function) as an alternative to Options API — enables better code organization, TypeScript integration, and logic reuse via composables. Vue 2 only had Options API. Reactivity system: Vue 2 used Object.defineProperty for reactivity — couldn't detect property addition/deletion, required Vue.set(). Vue 3 uses ES2015 Proxy — detects all mutations including property addition and deletion, works on arrays natively. Performance: Vue 3 is significantly faster — smaller virtual DOM, compiler optimizations (static tree hoisting, patch flags), tree-shakeable core (~20KB gzipped vs ~30KB for Vue 2). TypeScript: Vue 3 was written in TypeScript and has excellent TS support. Vue 2's TS support required decorators and was clunky. Multiple root elements: Vue 3 supports multiple root elements (fragments) in templates. Vue 2 required a single root element. Teleport: Vue 3 has a built-in Teleport component (like React portals). Suspense: experimental async component loading with loading/error states. Breaking changes: lifecycle hook renames (destroyed → unmounted), v-model changes, filter removal. Vue 2 reached end-of-life December 31, 2023.

Open this question on its own page
03

What is the Vue instance and how do you create one?

A Vue instance (or application instance in Vue 3) is the root of a Vue application — it connects Vue to a DOM element and manages the application's reactivity and component tree. Vue 3 (createApp): import { createApp } from "vue"; import App from "./App.vue"; const app = createApp(App); app.use(router).use(pinia); app.mount("#app");. createApp() creates a new application instance. mount() connects it to a DOM element (selector or element). The app instance provides methods: app.component(), app.directive(), app.provide(), app.use(), app.config.errorHandler. Vue 2 (new Vue): new Vue({ el: "#app", data: { message: "Hello" }, methods: { greet() {} } }). Application config (Vue 3): app.config.globalProperties.$http = axios; — adds global properties accessible in all components; app.config.errorHandler = (err) => { /* global error handler */ };; app.config.performance = true; — enable performance tracing. Multiple apps: Vue 3 supports multiple independent app instances on the same page, each with its own scope, plugins, and component tree.

Open this question on its own page
04

What are Vue components?

A Vue component is a reusable, self-contained piece of the UI with its own template, logic, and styles. Components are the building blocks of Vue applications. Single File Components (SFC — .vue files): the recommended format that combines template, script, and style in one file: <template> <div class="card"><h2>{{ title }}</h2></div> </template> <script setup> defineProps(["title"]); </script> <style scoped> .card { padding: 1rem; } </style>. Registering components: globally: app.component("MyCard", MyCard) — available everywhere without import; locally (recommended): import and declare in the component's script. In script setup, imported components are automatically available. Naming conventions: PascalCase for SFC files and component names; in templates both <MyCard /> and <my-card /> work. Component lifecycle: each component instance has its own lifecycle (created, mounted, updated, unmounted). Props and events: components communicate through props (parent → child data) and emits (child → parent events). Benefits: reusability, encapsulation, testability, maintainability. SFCs require a build step (Vite/Webpack with vue-loader); in-browser use without build: define component as an options object.

Open this question on its own page
05

What is Vue's reactivity system?

Vue's reactivity system automatically tracks which data is used by the view and updates the DOM when that data changes — without manually manipulating the DOM. Vue 3 reactivity (Proxy-based): when you declare reactive state, Vue wraps the object in an ES Proxy. When the proxy's get trap is triggered (reading a property), Vue records that this effect/component depends on this data. When the proxy's set trap is triggered (writing a property), Vue notifies all dependents to re-run. ref(): makes a single value reactive. Returns an object with a .value property: const count = ref(0); count.value++; // triggers update. In templates, .value is unwrapped automatically: {{ count }}. reactive(): makes an entire object reactive using Proxy: const state = reactive({ count: 0, name: "Alice" }); state.count++;. No .value needed. Limitation: loses reactivity if destructured (const { count } = state breaks reactivity — use toRefs()). Vue 2 reactivity: used Object.defineProperty with explicit get/set interceptors. Couldn't detect property addition (this.newProp = value — not reactive), needed Vue.set(). Array mutation methods were patched. Vue 3's Proxy solves all these limitations — any property access/mutation is intercepted, including new properties and array index assignments.

Open this question on its own page
06

What are props in Vue?

Props (properties) are custom attributes registered on a component that allow a parent to pass data down to a child component. Props flow unidirectionally — from parent to child. Declaring props (Composition API with script setup): const props = defineProps({ title: String, count: { type: Number, required: true, default: 0 }, user: { type: Object, validator: (val) => val.age >= 0 } });. With TypeScript: const props = defineProps<{ title: string; count?: number }>();. With defaults: const props = withDefaults(defineProps<{ count?: number }>(), { count: 0 });. Options API: props: { title: String, count: { type: Number, default: 0, required: true } }. Using props in parent: <UserCard :title="post.title" :count="post.views" :user="currentUser" />. Static prop (no colon): <UserCard title="Hello" /> — always a string. Prop types: String, Number, Boolean, Array, Object, Date, Function, Symbol, or custom class. Prop mutation: never mutate props directly — they are read-only in the child. To "mutate," emit an event to the parent or create a local copy. Prop case: camelCase in JS (firstName), kebab-case in templates (:first-name="value") — Vue auto-converts.

Open this question on its own page
07

What are Vue directives?

Directives are special HTML attributes with the v- prefix that apply reactive behavior to DOM elements. Built-in directives: v-if / v-else-if / v-else: conditionally render elements (removes/creates from DOM): <div v-if="isLoggedIn">Welcome</div><div v-else>Login</div>; v-show: toggle visibility with CSS display (keeps in DOM): <div v-show="isVisible">. Use v-if for infrequently toggled elements, v-show for frequently toggled; v-for: render a list: <li v-for="(item, index) in items" :key="item.id">{{ item.name }}</li>. Always use :key; v-bind (shorthand :): bind an attribute or prop: <img :src="imageUrl" :class="{ active: isActive }">; v-on (shorthand @): listen to events: <button @click="handleClick">; v-model: two-way data binding: <input v-model="searchText">; v-text: set element text content; v-html: set innerHTML (XSS risk — use only with trusted content); v-once: render only once, skip future updates; v-memo: memoize a subtree, re-render only when dependencies change; v-cloak: hide uncompiled template until ready; v-pre: skip compilation for this element. Custom directives: register with app.directive("highlight", { mounted(el) { el.style.background = "yellow"; } }).

Open this question on its own page
08

What is the difference between v-if and v-show?

Both v-if and v-show control element visibility, but they work differently: v-if: conditionally renders the element — it is literally added to or removed from the DOM based on the condition. When the condition is false, the element and its component children are completely destroyed (component lifecycle hooks fire). When it becomes true, they are recreated from scratch. It is a "real" conditional rendering: <ExpensiveComponent v-if="isActive" />. Pros: lower initial render cost (don't render elements that start as false); Cons: higher toggle cost (destroy/recreate). v-show: always renders the element but toggles the CSS display property between none and its original value. The element stays in the DOM; component lifecycle runs once. <ExpensiveComponent v-show="isActive" />. Pros: fast toggle (just a CSS change); Cons: always incurs the initial render cost. When to use which: use v-if when: the condition rarely changes, the element is expensive and shouldn't be rendered when not needed, or the element contains sensitive content. Use v-show when: you need to toggle frequently (tabs, accordion) and the initial render cost is acceptable. v-if + v-for: avoid using on the same element — v-if has higher priority in Vue 3 (v-for in Vue 2). Instead, wrap in a template or filter the array in a computed property.

Open this question on its own page
09

What is v-model in Vue?

v-model creates a two-way data binding between a form input and a data property — when the user types, the data updates; when the data changes programmatically, the input updates. It is syntactic sugar for :value + @input. Native inputs: <input v-model="username"> is equivalent to <input :value="username" @input="username = $event.target.value">. Works with: text inputs, textarea, select, checkboxes (boolean or array), radio buttons. On custom components (Vue 3): v-model on a component binds to a prop named modelValue and listens for update:modelValue event. Child component: const props = defineProps(["modelValue"]); const emit = defineEmits(["update:modelValue"]);. Template: <input :value="modelValue" @input="emit('update:modelValue', $event.target.value)">. Or use defineModel() (Vue 3.4+): const model = defineModel(); then <input v-model="model">. Multiple v-models: <UserForm v-model:firstName="firstName" v-model:lastName="lastName" /> — named v-models. Modifiers: v-model.lazy (sync on change instead of input — less frequent), v-model.number (auto-cast to number), v-model.trim (auto-trim whitespace). Custom modifiers possible via modifiers prop.

Open this question on its own page
10

What are computed properties in Vue?

Computed properties are derived values that automatically update when their dependencies change. They are cached based on their reactive dependencies — only recomputed when dependencies change, unlike methods which re-execute every render. Composition API: import { ref, computed } from "vue"; const firstName = ref("Alice"); const lastName = ref("Johnson"); const fullName = computed(() => `${firstName.value} ${lastName.value}`);. Options API: computed: { fullName() { return this.firstName + " " + this.lastName; } }. vs Methods: methods re-execute every time the template renders (no caching). {{ expensiveCalc() }} recalculates on every render; {{ expensiveComputed }} only recalculates when its dependencies change. Use computed for derived state that's used multiple times. Use methods for operations with side effects or when you don't want caching. Writable computed: const fullName = computed({ get: () => firstName.value + " " + lastName.value, set: (val) => { const parts = val.split(" "); firstName.value = parts[0]; lastName.value = parts[1]; } });. Setting fullName splits it into first and last. Best practices: keep computed getters free of side effects (no mutations, no async operations). Use watchers for side effects triggered by data changes.

Open this question on its own page
11

What are watchers in Vue?

Watchers react to data changes and execute side effects (async operations, DOM manipulation, logging) when a reactive source changes. Unlike computed properties (derive value), watchers perform actions. watch() — Composition API: watches a specific source. import { watch, ref } from "vue"; const userId = ref(1); watch(userId, async (newId, oldId) => { userData.value = await fetchUser(newId); });. Watch reactive object: watch(() => state.user.name, (newName) => { console.log(newName); });. Watch multiple sources: watch([firstName, lastName], ([newFirst, newLast]) => { ... });. Options: { immediate: true } — run immediately on mount; { deep: true } — deeply watch nested objects; { once: true } — run only once (Vue 3.4+). watchEffect() — Composition API: automatically tracks all reactive dependencies used inside the callback: watchEffect(async () => { const data = await fetch(`/api/user/${userId.value}`); });. Runs immediately, re-runs whenever userId changes. No need to specify the source. Options API watcher: watch: { searchTerm: { handler(newVal) { this.fetchResults(newVal); }, immediate: true, deep: false } }. Stopping a watcher: const stop = watch(...); stop();. Watchers created in setup() are automatically stopped when the component unmounts. watchEffect vs watch: watchEffect is simpler (auto-tracks deps, immediate); watch gives more control (old + new values, specific sources).

Open this question on its own page
12

What are Vue lifecycle hooks?

Vue lifecycle hooks let you execute code at specific stages of a component's life. Vue 3 hooks (Composition API): onBeforeMount — before the component is mounted (DOM not yet created); onMounted — component is mounted (DOM available, use for DOM access, fetch data, subscriptions); onBeforeUpdate — before the component re-renders due to state change; onUpdated — after re-render (DOM updated); onBeforeUnmount — before the component is unmounted (cleanup: clear timers, cancel subscriptions); onUnmounted — component is unmounted (DOM removed); onErrorCaptured — catches errors from descendant components; onActivated / onDeactivated — when a component inside <KeepAlive> is activated/deactivated. Usage in script setup: import { onMounted, onUnmounted } from "vue"; onMounted(() => { document.addEventListener("click", handler); }); onUnmounted(() => { document.removeEventListener("click", handler); });. Options API hooks: created (instance created, data/computed available, no DOM), mounted, updated, beforeDestroy → Vue 3 renamed to beforeUnmount, destroyedunmounted. Setup() runs before beforeCreate and created — in Composition API, setup() replaces both. Most commonly used: onMounted (init, fetch data, DOM setup) and onUnmounted (cleanup).

Open this question on its own page
13

What is the Composition API in Vue 3?

The Composition API is a set of function-based APIs for writing Vue component logic, introduced in Vue 3 as an alternative to the Options API. It uses a setup() function (or <script setup> shorthand) where all reactive state, computed properties, watchers, and lifecycle hooks are defined using imported functions. Why Composition API? In the Options API, logic for one feature is split across data, methods, computed, watch, and lifecycle hooks. With Composition API, all logic for one feature lives together and can be extracted into a reusable composable function. script setup (recommended): <script setup> import { ref, computed, onMounted } from "vue"; const count = ref(0); const doubled = computed(() => count.value * 2); function increment() { count.value++; } onMounted(() => { console.log("Mounted"); }); </script>. Top-level bindings are automatically exposed to the template. Benefits: (1) Better TypeScript support (pure functions with types); (2) Logic reuse via composables (replace mixins); (3) Colocation of related logic; (4) Smaller production bundle (better tree-shaking). Comparison with Options API: both are fully supported; Options API is not deprecated. Choose Composition API for complex components, libraries, and TypeScript projects. Options API is fine for simple components and teams transitioning from Vue 2.

Open this question on its own page
14

What is the Options API in Vue?

The Options API organizes component logic into predefined options (sections): export default { name: "UserCard", components: { Avatar }, props: { user: Object }, data() { return { isExpanded: false, localCopy: "" }; }, computed: { fullName() { return this.user.first + " " + this.user.last; } }, watch: { "user.name"(newVal) { this.fetchDetails(newVal); } }, methods: { toggleExpand() { this.isExpanded = !this.isExpanded; }, async fetchDetails(name) { this.localCopy = await api.get(name); } }, created() { this.fetchDetails(this.user.name); }, mounted() { this.$nextTick(() => { this.$refs.input.focus(); }); } };. Options: name, components, directives, extends, mixins, props, emits, expose, setup, data, computed, methods, watch, provide/inject, lifecycle hooks (beforeCreate, created, beforeMount, mounted, beforeUpdate, updated, beforeUnmount, unmounted, errorCaptured, renderTracked, renderTriggered, activated, deactivated). this context: in Options API methods, this refers to the component instance. All data, props, computed, and methods are accessible via this. When to use: straightforward for simple components, easier for Vue 2 developers, good for teams new to Vue. Can be mixed with Composition API (setup() option works within Options API components).

Open this question on its own page
15

What are Vue slots?

Slots allow parent components to inject content into a child component's template — like "holes" in the child that the parent can fill. They make components more flexible and reusable. Default slot: Child template: <div class="card"><slot /></div>. Parent usage: <BaseCard><p>This content fills the slot</p></BaseCard>. Fallback content: <slot>Default content if nothing is provided</slot>. Named slots: Child: <div class="modal"> <slot name="header" /> <slot /> <slot name="footer" /> </div>. Parent: <BaseModal> <template #header><h2>Title</h2></template> <p>Body content</p> <template #footer><button>Close</button></template> </BaseModal>. #header is shorthand for v-slot:header. Scoped slots: child passes data back to the parent's slot content. Child: <slot :item="item" :index="i" />. Parent: <template #default="{ item, index }">{{ index }}: {{ item.name }}</template>. Scoped slots allow the parent to control how the child renders its data — powerful for data tables and list components. Dynamic slots: <template #[dynamicSlotName]>. $slots: access slots programmatically to check if content was provided: $slots.header.

Open this question on its own page
16

What is Vue Router?

Vue Router is the official routing library for Vue.js that enables navigation between views in a SPA without full page reloads. Setup: import { createRouter, createWebHistory } from "vue-router"; const router = createRouter({ history: createWebHistory(), routes: [ { path: "/", component: HomeView }, { path: "/users/:id", component: UserView }, { path: "/admin", component: AdminView, meta: { requiresAuth: true } }, { path: "/:pathMatch(.*)*", component: NotFoundView } ] }); app.use(router);. Router outlet: <RouterView /> — where matched components render. Navigation links: <RouterLink to="/users/123">User</RouterLink> — renders as <a>, handles active class automatically. <RouterLink :to="{ name: 'UserDetail', params: { id: 123 } }">. Programmatic navigation: const router = useRouter(); router.push("/home"); router.push({ name: "UserDetail", params: { id: 123 } }); router.replace("/login"); router.go(-1);. Route params: const route = useRoute(); route.params.id; route.query.search;. Lazy loading routes: component: () => import("./views/AdminView.vue") — splits into separate chunk. Navigation guards: router.beforeEach((to, from, next) => { if (to.meta.requiresAuth && !isLoggedIn) next("/login"); else next(); });. History modes: Hash (#) or HTML5 History API (requires server configuration for direct URL access).

Open this question on its own page
17

What is Vuex?

Vuex is Vue's official state management pattern and library (Flux/Redux-inspired) for managing global shared state. Core concepts: (1) State: single source of truth — the app's data: state: { users: [], loading: false }; (2) Getters: computed properties for the store — derived state: getters: { activeUsers: (state) => state.users.filter(u => u.active) }; (3) Mutations: synchronous functions that directly modify state — only way to change state: mutations: { SET_USERS(state, users) { state.users = users; } }. Commit: store.commit("SET_USERS", users); (4) Actions: handle async logic and commit mutations: actions: { async fetchUsers({ commit }) { const users = await api.getUsers(); commit("SET_USERS", users); } }. Dispatch: store.dispatch("fetchUsers"); (5) Modules: divide store into namespaced modules. Using in components: import { useStore } from "vuex"; const store = useStore(); store.state.users; store.getters.activeUsers; store.dispatch("fetchUsers"); store.commit("SET_USERS", []);. Vuex 4 works with Vue 3. Pinia is the successor: Vuex 5 was never released; Pinia (by the Vue team) is now the official recommended state management solution for Vue 3. Simpler API, no mutations (actions can mutate directly), better TypeScript support, and modular by default.

Open this question on its own page
18

What is Pinia?

Pinia is the official state management library for Vue 3, created by Eduardo San Martin Morote (core Vue team member). It is lighter, simpler, and has better TypeScript support than Vuex. Defining a store: import { defineStore } from "pinia"; export const useUserStore = defineStore("user", { state: () => ({ users: [], currentUser: null, loading: false }), getters: { activeUsers: (state) => state.users.filter(u => u.active) }, actions: { async fetchUsers() { this.loading = true; this.users = await api.getUsers(); this.loading = false; }, logout() { this.currentUser = null; } } });. Setup stores (Composition API style): export const useUserStore = defineStore("user", () => { const users = ref([]); const activeUsers = computed(() => users.value.filter(u => u.active)); async function fetchUsers() { users.value = await api.getUsers(); } return { users, activeUsers, fetchUsers }; });. Using in components: const store = useUserStore(); store.users; // state store.activeUsers; // getter store.fetchUsers(); // action store.$patch({ loading: false }); // patch multiple props. Pinia vs Vuex: No mutations (actions can directly modify state), simpler API, modular by design, better TypeScript inference, DevTools support, SSR-friendly. Install: npm install pinia; app.use(createPinia()).

Open this question on its own page
19

What are Vue template refs?

Template refs provide direct access to a DOM element or child component instance from the script. Useful for: focusing inputs programmatically, triggering animations, accessing third-party library instances, calling child component methods. Composition API: declare a ref with the same name as the template attribute: <script setup> import { ref, onMounted } from "vue"; const inputRef = ref(null); // null before mount onMounted(() => { inputRef.value.focus(); // DOM element available }); </script> <template><input ref="inputRef"></template>. Component ref: accessing a child component — gets the component's public interface (exposed properties via defineExpose): <ChildComponent ref="childRef">; childRef.value.publicMethod();. defineExpose(): by default, components using <script setup> are closed — their internal state isn't accessible via refs. Use defineExpose to selectively expose: defineExpose({ reset, focus });. Options API: this.$refs.inputRef.focus(). v-for with refs: <li v-for="item in items" :ref="el => { if (el) itemRefs[item.id] = el }"> — collect refs from list items. Important: template refs are only available after mounting — don't access in setup() directly, use onMounted().

Open this question on its own page
20

What is the provide/inject pattern in Vue?

provide/inject allows an ancestor component to provide data/functions that any descendant component can inject, without prop drilling (passing through every intermediate component). Provider (ancestor): import { provide, ref } from "vue"; const theme = ref("dark"); const toggleTheme = () => { theme.value = theme.value === "dark" ? "light" : "dark"; }; provide("theme", { theme, toggleTheme });. Consumer (any descendant): import { inject } from "vue"; const { theme, toggleTheme } = inject("theme");. Default value: inject("theme", "light") — if no provider found. App-level provide: app.provide("globalConfig", config) — available everywhere. Reactivity: to keep reactivity when injecting, provide refs or reactive objects (not raw values). If the consumer modifies the provided value, it affects the provider — to prevent this, provide a readonly wrapper: provide("count", readonly(count)). Symbol keys: use Symbol instead of strings to avoid naming collisions: export const themeKey = Symbol("theme"); provide(themeKey, theme);. vs Pinia/Vuex: provide/inject is for implicit dependency injection for component libraries (like passing a form context from parent form to nested field components). Pinia/Vuex is better for complex global application state. Vue Router and Pinia themselves use provide/inject under the hood.

Open this question on its own page
21

What is the key attribute in Vue?

The key attribute is a special hint for Vue's diffing algorithm that helps it identify which items in a list have changed, been added, or removed — enabling efficient DOM updates and correct component reuse. In v-for: always provide a unique :key when rendering lists: <li v-for="user in users" :key="user.id">{{ user.name }}</li>. Without :key, Vue uses an in-place patch strategy — it tries to reuse DOM nodes in place, which is efficient but can cause bugs with stateful components or inputs. With :key, Vue matches each element to its key — moved elements are preserved, new elements are created, removed elements are destroyed. When keys are critical: (1) List items with state (form inputs, component state) — without key, states get mixed up when list order changes; (2) Animated list transitions — keys track elements for enter/leave animations; (3) Conditionally rendered components — different keys force recreation. Using key to force re-render: change the key on any element/component to force it to be destroyed and recreated: <UserForm :key="userId" /> — when userId changes, the form is completely recreated (resetting all state). This is a useful pattern for resetting forms. Key uniqueness: keys only need to be unique among siblings, not globally. Never use the array index as a key if the list can be reordered or filtered — use stable unique identifiers.

Open this question on its own page
22

What is Vue's v-bind and object syntax?

v-bind dynamically binds an expression to an attribute or component prop. Shorthand: :. Single attribute: <img :src="imageUrl">; <a :href="linkUrl">; <button :disabled="isLoading">. Object syntax for class binding: :class="{ active: isActive, 'text-error': hasError, disabled: isDisabled }" — keys are class names, values are boolean conditions. Multiple classes: combine with string or array: :class="[baseClass, { active: isActive }]". Object syntax for style binding: :style="{ color: textColor, fontSize: fontSize + 'px', backgroundColor: isDark ? '#000' : '#fff' }". Array syntax: :style="[baseStyles, conditionalStyles]" — merged. Spread all properties of an object as attributes: v-bind="attributeObject" — bind multiple attributes at once: const attrs = { id: "main", class: "card", href: "/about" }; <a v-bind="attrs">. Useful for wrapping/proxy components that pass attributes through. Modifier .prop: :somedom.prop="value" — force binding as DOM property instead of HTML attribute. Modifier .attr: force binding as HTML attribute. Dynamic attribute name: :[attributeName]="value" — compute the attribute name dynamically (square brackets).

Open this question on its own page
23

What is Vue event handling?

Vue event handling listens to DOM events and runs handlers using the v-on directive (shorthand: @). Inline handlers: <button @click="count++"> — simple expressions; <button @click="handleClick"> — method reference; <button @click="handleClick($event, extraArg)"> — pass arguments. Method handlers: if the expression is a method name (no parentheses), Vue automatically passes the native DOM event as the first argument. Event modifiers: chain modifiers to the event for common operations: @click.stop — call event.stopPropagation(); @click.prevent — call event.preventDefault() (useful for forms: @submit.prevent="onSubmit"); @click.self — only trigger if event target is the element itself (not bubbled from child); @click.once — trigger at most once; @click.passive — improves scroll performance; @click.capture — use capture mode. Key modifiers: @keyup.enter="submit", @keyup.esc="cancel", @keydown.ctrl.s="save" — listen for specific keys. Named aliases: enter, tab, delete, esc, space, up, down, left, right. System modifiers: ctrl, alt, shift, meta. Custom events: components use emit to dispatch events: child: emit("update", newValue); parent: @update="handleUpdate". defineEmits for declaring: const emit = defineEmits(["update", "delete"]);.

Open this question on its own page
24

What is the Vue transition system?

Vue's built-in Transition and TransitionGroup components provide animation and transition effects when elements are inserted, updated, or removed. <Transition> component: wraps a single element that toggles with v-if/v-show or dynamic components. Vue automatically applies CSS classes at different transition stages: v-enter-from (initial state), v-enter-active (while entering), v-enter-to (end state), v-leave-from, v-leave-active, v-leave-to. Example: <Transition name="fade"><div v-if="show">Content</div></Transition>. CSS: .fade-enter-active, .fade-leave-active { transition: opacity 0.3s; } .fade-enter-from, .fade-leave-to { opacity: 0; }. The name prefix ("fade") matches the transition name attribute. Custom transition classes: specify classes for third-party animation libraries (Animate.css): <Transition enter-active-class="animate__animated animate__fadeIn">. JavaScript hooks: use @enter, @leave, @after-enter hooks for programmatic animations (GSAP, Web Animations API). <TransitionGroup>: for animating v-for lists. Renders as a <ul> or other element by default. Adds v-move class for elements that change position (smooth reordering with FLIP technique). appear: add appear attribute for transition on initial render: <Transition appear>. KeepAlive transitions: onActivated/onDeactivated hooks fire instead of onMounted/onUnmounted.

Open this question on its own page
25

What is &lt;KeepAlive&gt; in Vue?

<KeepAlive> is a built-in Vue component that caches (keeps alive) component instances when they are switched out of a dynamic component or route view, instead of destroying and recreating them. Usage: wrap dynamic component or RouterView: <KeepAlive><component :is="activeTab" /></KeepAlive>. Or with Vue Router: <RouterView v-slot="{ Component }"><KeepAlive><component :is="Component" /></KeepAlive></RouterView>. Effect: when a component is "kept alive," switching away doesn't unmount it — its state (scroll position, form data, timer state) is preserved. Switching back shows the same state. The component goes inactive (onDeactivated fires) and back to active (onActivated fires) instead of unmounted/mounted. include/exclude: selectively cache components: <KeepAlive include="UserList,ProductList"> (comma-separated component names), or RegExp, or array. max: limit the number of cached instances: <KeepAlive :max="10"> — when limit exceeded, the least recently used instance is destroyed. Use cases: tabbed interfaces (preserve tab content state), search results (go back without refetch), multi-step forms (preserve earlier step state while navigating). Cost: cached components consume memory — only keep-alive what provides clear UX benefit.

Open this question on its own page
26

What is the defineProps and defineEmits in Vue 3 script setup?

In <script setup>, defineProps and defineEmits are compiler macros for declaring a component's props and custom events. They don't need to be imported. defineProps: // Runtime declaration: const props = defineProps({ title: String, count: { type: Number, required: true, default: 0 }, items: Array }); // TypeScript declaration (preferred): const props = defineProps<{ title: string; count?: number; items: string[]; }>(); // With defaults (TS): const props = withDefaults(defineProps<{ count?: number }>(), { count: 0 });. Access in template directly: {{ title }}. In script: props.count. defineEmits: // Runtime: const emit = defineEmits(["update:modelValue", "submit", "close"]); // TypeScript: const emit = defineEmits<{ "update:modelValue": [value: string]; submit: [data: FormData]; close: []; }>();. Usage: emit("update:modelValue", newValue);. defineModel() (Vue 3.4+): convenient macro for v-model: const model = defineModel<string>(); // access/set model.value. Creates the modelValue prop and update:modelValue emit automatically. With custom name: const visible = defineModel<boolean>("visible"). Type-only props validation: TypeScript declaration is safer and gives better IDE support than runtime declaration.

Open this question on its own page
27

What is Vue's Teleport component?

<Teleport> (Vue 3) renders a component's content in a different DOM location than where it's declared in the component tree — "teleporting" it to another DOM node. Usage: <Teleport to="body"> <div class="modal"> <h2>Modal Title</h2> <p>Modal content</p> </div> </Teleport>. The to attribute is a CSS selector string (or DOM element). The modal is rendered as a child of <body> in the actual DOM, even though it's declared inside a deeply nested component. Why is this useful? (1) Modals and overlays: modals should render at the document root to avoid CSS stacking context issues (overflow: hidden on parents, z-index problems, transforms affecting fixed positioning); (2) Tooltips and popovers: same reasoning; (3) Notifications/toasts: always render in a fixed position relative to the viewport. Still in component tree: despite teleporting, the component's logic (props, events, provide/inject, lifecycle) remains in the original component tree — only the rendered output is moved. Parent context (theme, auth) is still accessible. disabled: <Teleport :disabled="isMobile" to="body"> — conditionally disable teleportation. Multiple Teleports: multiple teleports to the same target are appended in order. Equivalent in React: React Portals (ReactDOM.createPortal).

Open this question on its own page
28

What are Vue composables?

Composables are reusable functions that use Vue's Composition API to encapsulate and share stateful logic between components — the Vue 3 replacement for Vue 2 mixins. A composable is a plain JavaScript function (by convention prefixed with "use") that uses reactive primitives (ref, computed, watch, lifecycle hooks) internally. Example composable: // useFetch.js export function useFetch(url) { const data = ref(null); const loading = ref(true); const error = ref(null); async function fetchData() { loading.value = true; try { data.value = await (await fetch(url)).json(); } catch(e) { error.value = e; } finally { loading.value = false; } } onMounted(fetchData); return { data, loading, error, refetch: fetchData }; }. Using in a component: import { useFetch } from "./composables/useFetch"; const { data: users, loading, error } = useFetch("/api/users");. vs Mixins (Vue 2): mixins have unclear sources of data and name collisions (two mixins with the same property name). Composables have explicit returns — you always know where a value came from. Composables can accept parameters; mixins can't. Popular patterns: useLocalStorage, useEventListener, useMouse, useMediaQuery, useDebounce, useIntersectionObserver. VueUse (vueuse.org) is a collection of 200+ ready-made composables.

Open this question on its own page
29

What are custom directives in Vue 3?

Custom directives allow you to define reusable low-level DOM manipulation logic. Define with lifecycle hooks that receive the DOM element. Global registration: app.directive("focus", { mounted(el) { el.focus(); } });. Usage: <input v-focus>. Directive hooks: app.directive("color", { created(el, binding, vnode) {}, // before attributes are applied beforeMount(el, binding) {}, mounted(el, binding) { el.style.color = binding.value; }, beforeUpdate(el, binding) {}, updated(el, binding) {}, beforeUnmount(el, binding) {}, unmounted(el, binding) {} });. Binding object: binding.value — the directive value: v-color="'red'"; binding.arg — the argument after colon: v-color:background; binding.modifiers — object of modifiers: v-color.once; binding.oldValue — previous value (in updated hook); binding.instance — component instance. Local registration in script setup: const vFocus = { mounted: (el) => el.focus() }; — any variable starting with v is treated as a directive. Shorthand (same for mounted and updated): app.directive("color", (el, binding) => { el.style.color = binding.value; }). Common uses: auto-focus, tooltip, lazy image loading, click-outside detection, intersection observer, copy to clipboard.

Open this question on its own page
30

What are Vue filters and why were they removed in Vue 3?

Filters were a Vue 2 feature for applying text formatting in template expressions using the pipe operator: {{ price | currency }} {{ date | formatDate("DD/MM") }}. They could be chained: {{ text | capitalize | truncate(50) }}. Defining filters (Vue 2): global: Vue.filter("currency", (value) => "$" + value.toFixed(2)); local: filters: { currency(value) { return "$" + value.toFixed(2); } }. Why removed in Vue 3: (1) Inconsistent with JavaScript — the pipe operator (|) in Vue templates conflicted with JavaScript's bitwise OR operator, causing confusion; (2) Filters break when templates are used in IDE environments expecting valid JavaScript expressions; (3) The functionality is easily replaced by computed properties and methods which are more flexible; (4) Filters add complexity to the template compilation. Migration to Vue 3: replace filters with: (1) Computed properties: for frequently used transformations on data; (2) Methods: for parameterized transformations: {{ formatCurrency(price) }}; (3) Global helper functions: import and use utility functions; (4) Pipes via composables or global properties: app.config.globalProperties.$filters = { currency, formatDate } — accessible as {{ $filters.currency(price) }}.

Open this question on its own page
31

What is Vue's nextTick?

nextTick is a utility that defers a callback until after the next DOM update cycle. Vue batches state changes and applies DOM updates asynchronously. This means after you change data, the DOM isn't immediately updated — Vue schedules it. If you need to access the updated DOM immediately after a state change, use nextTick. Usage: import { nextTick, ref } from "vue"; const message = ref("old"); async function changeMessage() { message.value = "new"; await nextTick(); // DOM has been updated console.log(document.getElementById("msg").textContent); // "new" }. Callback syntax: nextTick(() => { // callback after DOM update });. Options API: this.$nextTick(() => { this.$refs.input.focus(); });. Common use cases: (1) Focus an input after it's conditionally rendered: change v-if to true → DOM not yet updated → nextTick → element exists → focus it; (2) Scroll to a newly added item in a list; (3) Access the scrollHeight of an element after content was added; (4) Trigger animations after DOM updates; (5) Tests that need to wait for Vue to process changes. Why Vue batches DOM updates: if multiple data properties change in the same synchronous block, Vue only updates the DOM once at the end of the microtask queue — more efficient. nextTick waits for this flush to complete.

Open this question on its own page
Intermediate 14 questions

Practical knowledge for developers with hands-on experience.

01

What is the Composition API and how do composables work?

The Composition API is a set of reactive primitives and lifecycle hooks that can be organized in a setup() function or <script setup>. Composable pattern: a composable is a function that uses Composition API internally to encapsulate stateful, reusable logic. It can accept reactive arguments and return reactive state. Advanced composable example — useIntersectionObserver: export function useIntersectionObserver(target, options = {}) { const isIntersecting = ref(false); let observer; onMounted(() => { observer = new IntersectionObserver((entries) => { isIntersecting.value = entries[0]?.isIntersecting ?? false; }, options); if (target.value) observer.observe(target.value); }); onUnmounted(() => observer?.disconnect()); return { isIntersecting }; }. Usage: const el = ref(null); const { isIntersecting } = useIntersectionObserver(el); <div ref="el" :class="{ visible: isIntersecting }">. Accepting reactive arguments: composables should use toValue() (Vue 3.3+) or isRef() to handle both plain values and refs as arguments: const url = computed(() => `/api/${toValue(id)}`);. Side-effect cleanup: always clean up in onUnmounted. async composables: can use await but the async suspense integration requires special handling. Testing composables: test in isolation — create a test component or use withSetup(fn) utility.

Open this question on its own page
02

What is Vue's reactivity in depth — ref vs reactive?

Understanding when to use ref() vs reactive() is important for Vue 3 Composition API: ref(): wraps a value in a reactive wrapper object with a .value property. Works for ANY type (primitives, objects, arrays). const count = ref(0); const user = ref({ name: "Alice" }); count.value++; user.value.name = "Bob";. In templates, .value is automatically unwrapped. Refs maintain identity — reassigning user.value = newUser is reactive. reactive(): creates a reactive proxy of a plain object (not for primitives — they can't be proxied). Returns the proxy directly (no .value). const state = reactive({ count: 0, name: "Alice" }); state.count++;. Key limitation of reactive(): losing reactivity when destructuring or when the reactive object is reassigned: const { count } = state; // count is no longer reactive!. Fix: use toRefs(state) which converts each property to a ref: const { count, name } = toRefs(state); — count.value is now reactive. When to use which: ref for primitive values, standalone variables; reactive for objects with related properties treated as a unit. Many Vue developers prefer using ref for everything (more consistent, easier to track) — the auto-unwrapping in templates removes the .value awkwardness. Vue team recommends ref() as the primary API. toRef(): create a ref for a single property of a reactive object: const count = toRef(state, "count");

Open this question on its own page
03

What are Vue 3 Teleport and Suspense?

Teleport renders content in a different DOM location. Suspense (experimental) handles async dependencies in a component tree, showing fallback content while async components or setup() functions resolve. Suspense usage: wrap async components: <Suspense> <template #default> <AsyncDashboard /> </template> <template #fallback> <Spinner /> </template> </Suspense>. The default slot contains the async content; fallback is shown while loading. What makes a component "async" for Suspense: (1) Async setup() function: async setup() { const data = await fetchData(); return { data }; }; (2) Async component: defineAsyncComponent(() => import("./Heavy.vue")). Suspense events: @pending — async dep started, @resolve — all resolved, @fallback — fallback shown. Error handling with Suspense: combine with error boundaries using onErrorCaptured or <Suspense> + error template. Nested Suspense: each Suspense manages its own async children. Current status: Suspense is experimental — API may change. Not recommended for critical production use without careful testing. Teleport + Suspense: Teleport's async component contents participate in the parent's Suspense boundary even after teleporting. defineAsyncComponent: defineAsyncComponent({ loader: () => import("./Heavy.vue"), loadingComponent: Spinner, errorComponent: ErrorFallback, delay: 200, timeout: 3000 }).

Open this question on its own page
04

What is Vue's virtual DOM and how does the diffing algorithm work?

Vue's Virtual DOM (VDOM) is an in-memory JavaScript representation of the actual DOM. When component state changes, Vue re-renders the component template to produce a new virtual DOM tree, then diffs it against the previous tree and patches only the changed parts of the real DOM. VDOM node (VNode): a plain JavaScript object describing a DOM node: { type: "div", props: { class: "card", onClick: handler }, children: [{ type: "span", props: {}, children: "Hello" }] }. Diffing algorithm: Vue's diff compares old and new VNode trees with these heuristics: (1) Nodes of different types (div → span) → completely replace; (2) Same type nodes → update props/attributes in place; (3) Children diffing: without keys — simple sequential comparison (may produce incorrect results with stateful nodes), with keys — uses a "longest increasing subsequence" algorithm to minimize moves. Vue 3 optimizations (Compiler-informed): Vue 3's template compiler analyzes templates at compile time and adds hints: (1) Static hoisting: static nodes (no bindings) are lifted out of the render function — created once, reused; (2) Patch flags: nodes with dynamic content get a flag indicating WHAT is dynamic (CLASS, STYLE, PROPS, TEXT) — only those parts are diffed at runtime; (3) Block tree: only tracks dynamic children in stable structural blocks. These optimizations make Vue 3's runtime patching significantly faster than runtime-only diff algorithms (React).

Open this question on its own page
05

What is Vue's reactivity with watchEffect and its nuances?

watchEffect automatically tracks its reactive dependencies (like computed) but runs as a side-effect (like watch) — it re-runs the callback whenever any reactive values it reads change. Key behaviors: (1) Immediate: runs once immediately when first called (no immediate: true needed like watch); (2) Auto-tracking: any reactive value (ref, reactive, computed, route, store) accessed inside the callback is automatically tracked; (3) Re-runs on any dependency change; (4) Cleanup: return a cleanup function from the callback for teardown between runs: watchEffect((onCleanup) => { const timer = setTimeout(() => { fetchData(id.value); }, 300); onCleanup(() => clearTimeout(timer)); });. Timing options: { flush: "post" } — run after component DOM update (access updated DOM), equivalent to watchPostEffect(); { flush: "sync" } — synchronous, fires immediately on every change (use rarely); default: "pre" — before component re-renders. watchPostEffect: alias for watchEffect with { flush: "post" } — useful for accessing updated DOM or child component state. watchSyncEffect: alias for { flush: "sync" }. Stopping: const stop = watchEffect(() => {}); stop(); — auto-stopped when component unmounts if called in setup. If called outside setup(), must be stopped manually. watchEffect vs watch: watchEffect doesn't give old/new values; watch is more explicit about what to watch; watchEffect is simpler for multiple dependencies.

Open this question on its own page
06

What is Vue's component communication patterns?

Components communicate through several patterns based on their relationship: 1. Parent → Child: Props. Pass data down via props. Child receives and displays/uses them. 2. Child → Parent: Emits. Child emits custom events; parent listens with v-on/@. defineEmits(["update"]); emit("update", data); — parent: @update="handler". 3. v-model: Two-way binding. Combines props + emit for form-like components. 4. Provide/Inject: Ancestor → Any Descendant. Provider component provides data; any descendant can inject without prop drilling. Useful for global config, themes, form contexts. 5. Pinia/Vuex: Global State. Shared state store accessible from any component. Best for app-wide data (user, cart, notifications). 6. Template refs: Direct component access. Parent accesses child's exposed methods via ref. 7. Event bus (anti-pattern for Vue 3): Vue 2 used a Vue instance as a global event bus. In Vue 3 (no separate Vue instance), use: a tiny mitt library, Pinia actions, or provide/inject with a reactive EventEmitter. 8. Composables with shared state: a composable can hold module-level reactive state shared across all uses: const state = ref([]); export function useSharedList() { return { list: state, add: (item) => state.value.push(item) }; }. All components using this composable share the same state. 9. Props drilling alternative: for deeply nested data, prefer provide/inject or Pinia over passing props through many layers.

Open this question on its own page
07

What are Vue Router navigation guards in detail?

Vue Router navigation guards hook into the navigation process to allow, redirect, or cancel navigation. Global guards (on the router object): router.beforeEach((to, from, next) => { if (to.meta.requiresAuth && !store.isLoggedIn) { return "/login"; // redirect } return true; // allow }). Can return false (cancel), a route location object (redirect), or true/undefined (allow). next() legacy API still works but returning is cleaner. router.afterEach((to, from, failure) => { sendAnalytics(to.path); }). router.beforeResolve — called after all in-component guards and async route components are resolved. Per-route guards: defined on the route object: { path: "/admin", component: AdminView, beforeEnter(to, from) { if (!isAdmin) return "/403"; } }. Array of guards: beforeEnter: [checkAuth, checkPermission]. In-component guards (Options API): beforeRouteEnter(to, from, next) { next(vm => { vm.doSomething(); }); } — only guard that can access the component instance via callback; beforeRouteUpdate — when navigating to the same component with different params (/user/1 → /user/2); beforeRouteLeave — before leaving, use for unsaved changes warning. Composition API (setup): onBeforeRouteLeave((to, from) => { if (unsavedChanges) return false; });. Route meta: custom data on routes: meta: { requiresAuth: true, roles: ["admin"] } — use in guards for permission checking.

Open this question on its own page
08

What is Vue's server-side rendering (SSR) with Nuxt?

Nuxt.js is a meta-framework built on Vue that enables server-side rendering (SSR), static site generation (SSG), and other full-stack features with Vue. Rendering modes: (1) Universal (SSR): renders on server first, then hydrates on client. Best for SEO and initial load performance; (2) SPA: renders entirely on client (no SSR). Like plain Vue; (3) Static (SSG): pre-renders all pages at build time to static HTML files. Best performance, no server needed; (4) Hybrid rendering (Nuxt 3): configure per-route — some routes SSR, others static, others SPA. Nuxt 3 features: auto-imports (no explicit imports for components, composables), file-based routing (files in pages/ → routes), layouts, middleware, plugins, server API routes (server/api/ files), nuxt.config.ts for configuration. useAsyncData / useFetch: const { data, pending, error } = await useFetch("/api/posts") — fetches on server during SSR, uses cache on client navigation. Deduplicates between server and client. useState: const counter = useState("counter", () => 0) — SSR-friendly reactive state that persists from server to client during hydration. SSR considerations: no window/document in server context; use process.client guard; useHead() for meta tags; cookies accessible on both client and server; nuxt-auth, nuxt-content, nuxt-image ecosystem modules.

Open this question on its own page
09

What is the Vue 3 Suspense API and async components?

defineAsyncComponent creates a component that is loaded asynchronously (code-split). import { defineAsyncComponent } from "vue"; const HeavyChart = defineAsyncComponent({ loader: () => import("./HeavyChart.vue"), loadingComponent: LoadingSpinner, errorComponent: ErrorDisplay, delay: 200, // show loading only if takes > 200ms timeout: 10000, // error after 10s onError(error, retry, fail, attempts) { if (attempts <= 3) retry(); else fail(); } });. Without the options object: const LazyComponent = defineAsyncComponent(() => import("./Lazy.vue")). Async setup() for Suspense: components with top-level await in setup() are "async components" for Suspense: <script setup> const data = await fetch("/api/data").then(r => r.json()); // top-level await </script>. This component participates in parent Suspense boundary. Combining with Suspense: <Suspense> <template #default> <AsyncDataComponent /> </template> <template #fallback> <p>Loading...</p> </template> </Suspense>. SSR integration: Nuxt's useFetch and useAsyncData integrate with Suspense boundaries for server-side data fetching. Limitations: Suspense is experimental — avoid using in deeply nested async setups in production until stable. Works well for page-level async data loading.

Open this question on its own page
10

How does Vue 3 handle TypeScript?

Vue 3 was rewritten in TypeScript and has first-class TypeScript support. Script setup with TypeScript: the most ergonomic approach — TypeScript works naturally in <script setup lang="ts">. Typed props: interface User { id: number; name: string; email?: string; } const props = defineProps<{ user: User; count?: number; items: string[] }>(); withDefaults(defineProps<{ count?: number }>(), { count: 0 });. Typed emits: const emit = defineEmits<{ "update:modelValue": [value: string]; submit: [data: FormData] }>();. Typed refs: const count = ref<number>(0); const user = ref<User | null>(null); const inputEl = ref<HTMLInputElement | null>(null);. Typed reactive: const state = reactive<{ users: User[]; loading: boolean }>({ users: [], loading: false });. Typed provide/inject: use InjectionKey symbol: export const userKey: InjectionKey<User> = Symbol("user"); provide(userKey, user); const user = inject(userKey); — TypeScript infers the type. Typed component ref: const childRef = ref<InstanceType<typeof ChildComponent> | null>(null);. PropType for runtime validation: defineProps({ user: { type: Object as PropType<User>, required: true } }). Component type: import type { ComponentPublicInstance } from "vue";. Shims: vue-tsc for type-checking templates (runs in CI/CD). npx vue-tsc --noEmit.

Open this question on its own page
11

What is Vue 3 performance optimization techniques?

Vue 3 performance optimization strategies: 1. v-once and v-memo: v-once renders element once and skips future updates. v-memo="[dep1, dep2]" — memoize subtree, only re-render when dependencies change: useful in v-for lists: <li v-for="item in list" :key="item.id" v-memo="[item.isSelected]">. 2. Virtual scrolling: for large lists (10,000+ items), only render visible items using vue-virtual-scroller or @tanstack/virtual. 3. Lazy loading: lazy-load routes and heavy components with defineAsyncComponent and dynamic imports. 4. Computed properties over methods: computed caches results until dependencies change; methods recalculate every render. 5. shallowRef / shallowReactive: for large objects where deep reactivity is not needed: const data = shallowRef(bigObject) — only top-level property changes are reactive. 6. Non-reactive large data: if data doesn't need reactivity (third-party chart instances, non-UI data), use markRaw(): const chart = markRaw(new Chart(...)); — skips proxy wrapping. 7. Async components: split heavy components into separate chunks. 8. Bundle size: tree-shake Vue features not used (Vue 3 is fully tree-shakeable). 9. Event handler caching: Vue 3 compiler caches inline handlers: @click="() => foo()" is cached. 10. SSR + prerender: for content-heavy pages, SSR/SSG dramatically improves FCP. 11. Vite for dev: Vite's native ESM dev server is 10-100x faster than Webpack for HMR.

Open this question on its own page
12

What is Vue's scoped slots pattern?

Scoped slots are a powerful pattern where the child component passes data back to the parent's slot content — enabling the parent to control rendering while the child controls data. This "inverts control" for flexible, reusable components. Pattern: Renderless component (headless component): a component that provides behavior/data/logic but no UI. All the UI is defined by the parent via scoped slots. // UseMouseTracker.vue <template> <slot :x="x" :y="y" /> </template> <script setup> const x = ref(0), y = ref(0); const update = (e) => { x.value = e.clientX; y.value = e.clientY; }; onMounted(() => document.addEventListener("mousemove", update)); onUnmounted(() => document.removeEventListener("mousemove", update)); </script>. Parent usage: <UseMouseTracker v-slot="{ x, y }"> <p>Mouse: {{ x }}, {{ y }}</p> </UseMouseTracker>. DataTable with scoped slots: <DataTable :data="users"> <template #cell-name="{ row }"> <Avatar :user="row" /> {{ row.name }} </template> </DataTable>. The table handles sorting/pagination; the parent controls cell rendering. Modern alternative: composables replace renderless components in many cases — same logic reuse without wrapper components. Use renderless components when the component controls DOM structure/events that composables can't easily handle.

Open this question on its own page
13

What is Pinia in depth?

Pinia (the official Vue state management library) has a simple, flat API: State: reactive data defined in state(). Access directly: store.count. Can be mutated directly in actions or from components (unlike Vuex mutations). Direct mutation: store.count++ (works but not recommended — use actions for traceability). $patch: efficiently update multiple state properties at once: store.$patch({ loading: false, users: newUsers }) or with a function for complex mutations: store.$patch((state) => { state.items.push(newItem); state.loading = false; });. $reset(): reset store to initial state (only in Options stores, not Setup stores). $subscribe(): watch store changes: store.$subscribe((mutation, state) => { localStorage.setItem("users", JSON.stringify(state.users)); });. $onAction(): track actions being called: store.$onAction(({ name, args, after, onError }) => { after(result => { /* log success */ }); onError(err => { /* log error */ }); });. Store composition: use other stores inside a store: const authStore = useAuthStore(); const { user } = storeToRefs(authStore);. Plugins: extend all stores with additional capabilities: pinia.use(({ store }) => { store.router = markRaw(router); });. Persistence plugin: pinia-plugin-persistedstate automatically persists store to localStorage. storeToRefs(): destructure store reactively: const { users, loading } = storeToRefs(store); — maintains reactivity (plain destructuring loses it).

Open this question on its own page
14

What are advanced Vue Router patterns?

Advanced Vue Router patterns: 1. Nested routes: child routes within a parent component. Parent template must include a <RouterView />: { path: "/user/:id", component: UserLayout, children: [ { path: "", component: UserProfile }, { path: "posts", component: UserPosts } ] }. 2. Named routes: { path: "/user/:id", name: "UserDetail", component: UserDetail }. Navigate by name: router.push({ name: "UserDetail", params: { id } }). 3. Named views: render multiple components simultaneously: <RouterView name="sidebar" /> <RouterView /> — route: components: { default: Main, sidebar: Sidebar }. 4. Router meta + middleware: add meta to routes, process in beforeEach. Create a layered guard system. 5. Scroll behavior: scrollBehavior(to, from, savedPosition) { if (savedPosition) return savedPosition; return { top: 0 }; } — restore scroll on browser back. 6. Dynamic route addition: router.addRoute({ path: "/new", component: New }); router.removeRoute("RouteName"). Useful for permission-based route setup after login. 7. Route transition: use <RouterView v-slot="{ Component }"><Transition name="fade"><component :is="Component" /></Transition></RouterView>. 8. Typed routes (unplugin-typed-router): TypeScript types for route names and params. 9. View transitions API (experimental): native browser transition API with Vue Router integration for page transitions without CSS.

Open this question on its own page
Advanced 6 questions

Deep expertise questions for senior and lead roles.

01

How does Vue 3's Proxy-based reactivity system work internally?

Vue 3's reactivity is powered by ES2015 Proxy, which intercepts all operations on an object. Core implementation: (1) reactive(obj): wraps obj in a Proxy with handlers for get, set, deleteProperty, has, and ownKeys traps; (2) Track (in get trap): when a property is read, Vue calls track(target, "get", key) — this records that the currently running effect (computed/watch) depends on this property. Dependencies are stored in a WeakMap: target → Map: key → Set<Effect>; (3) Trigger (in set/delete trap): when a property is written, Vue calls trigger(target, "set", key) — this finds all effects that depend on this target.key and re-runs them; (4) ref(val): a ref is an object with a .value getter/setter. The getter calls track(), the setter calls trigger(). For object values, ref wraps them in reactive(). Primitives don't need Proxy — just get/set interception; (5) computed(fn): creates an effect that only re-runs when its dependencies change. Lazy — only evaluates when read. Caches result; re-evaluates when dependencies change; (6) Effect: a function (watcher/computed) that runs within a tracked context. During execution, all reactive reads are recorded as dependencies. Nested effects: Vue maintains an active effect stack — computed within a watch works correctly. Raw access: toRaw(proxy) — access the original object without tracking. markRaw(obj) — prevent object from being made reactive.

Open this question on its own page
02

What is Vue's compiler and how does it optimize templates?

Vue 3's template compiler converts HTML-like templates into optimized JavaScript render functions. Key optimizations: 1. Static Hoisting: elements with no dynamic bindings are identified at compile time and hoisted outside the render function as module-level constants — created once per app, not every render: const _hoisted_1 = createVNode("h1", null, "Static Title"). The render function just references _hoisted_1. 2. Patch Flags: dynamic nodes get a numeric flag indicating WHAT can change. The runtime only checks flagged properties: FLAG = 2 (class is dynamic), 4 (style is dynamic), 8 (specific props), 1 (text content). Compound: 9 = class + text. Runtime uses bitwise AND to check: if (vnode.patchFlag & 8) { /* update props */ }. Only flagged parts are diffed. 3. Block Tree: the compiler creates "block" nodes for structural elements (divs, sections). A block tracks its own dynamic descendant nodes in a flat array. Diffing is O(number of dynamic nodes) instead of O(total nodes). Static subtrees inside a block are never diffed. 4. Method/handler caching: inline event handlers are cached: @click="() => foo()" compiles to a cached function — same reference each render. 5. v-once compilation: marked nodes compile to a static creation with no re-rendering code. Impact: these compile-time optimizations mean Vue 3's runtime diffing operates on far fewer nodes than React's fiber reconciler (which has no compile-time information). Vue 3 is 1.3-2x faster than Vue 2 and comparable to the fastest virtual DOM implementations.

Open this question on its own page
03

How does Vue handle SSR hydration?

Vue SSR hydration is the process of attaching Vue's reactive data and event listeners to the server-rendered HTML, making it interactive without re-creating the DOM. SSR rendering pipeline: Node.js server renders Vue components to HTML string using renderToString(app) or renderToNodeStream() → HTML sent to browser → Vue client-side code loads → hydration runs. Hydration process: instead of app.mount("#app"), use: app.mount("#app") — Vue detects existing server-rendered HTML and walks the existing DOM, matching it with the VDOM nodes generated by rendering, attaching event listeners and reactive data. No DOM nodes are created — existing ones are reused. State transfer (useSSRContext / useState in Nuxt): data fetched on the server must be transferred to the client to prevent duplicate network requests. Vue SSR uses window.__INITIAL_STATE__ (or similar) to embed serialized state in the HTML. Client reads this to hydrate the store/state without re-fetching. Hydration mismatch: if the server-rendered HTML doesn't match what the client renders (different data, browser-only APIs, non-deterministic output), Vue logs a warning and falls back to client-side rendering. Avoid: browser-only APIs in SSR code (wrap in onMounted or process.client checks), non-deterministic components (random IDs, current time). Nuxt useHydration: hook into the hydration payload for custom data. Streaming SSR: renderToWebStream() in Vue 3 streams HTML progressively, sending content before the entire page is rendered.

Open this question on its own page
04

What is Vue's renderless component and headless UI pattern?

Renderless components (headless UI) separate behavior and state from the visual presentation. The component provides all the logic, state, and accessibility via scoped slots — consumers provide the markup. Advanced example — custom select/combobox: // HeadlessSelect.vue <script setup> const props = defineProps(["options", "modelValue"]); const emit = defineEmits(["update:modelValue"]); const isOpen = ref(false); const selected = computed(() => props.options.find(o => o.value === props.modelValue)); const activeIndex = ref(-1); function open() { isOpen.value = true; } function close() { isOpen.value = false; } function select(option) { emit("update:modelValue", option.value); close(); } function handleKeydown(e) { if (e.key === "ArrowDown") activeIndex.value = Math.min(activeIndex.value + 1, props.options.length - 1); if (e.key === "ArrowUp") activeIndex.value = Math.max(activeIndex.value - 1, 0); if (e.key === "Enter") select(props.options[activeIndex.value]); if (e.key === "Escape") close(); } </script> <template><slot :isOpen="isOpen" :selected="selected" :open="open" :close="close" :select="select" :handleKeydown="handleKeydown" :options="props.options" :activeIndex="activeIndex" /></template>. Usage: any visual style while keeping the same logic: <HeadlessSelect :options="opts" v-model="value" v-slot="{ isOpen, selected, open, close, select }"> <button @click="open">{{ selected?.label }}</button> <div v-if="isOpen"><button v-for="opt in opts" @click="select(opt)">{{ opt.label }}</button></div> </HeadlessSelect>. Headless UI components (Radix Vue, Headless UI for Vue) are production-ready implementations of this pattern with full accessibility.

Open this question on its own page
05

What is Vue's plugin system?

Vue plugins are objects (or functions) that add global-level functionality to an application. They receive the app instance and options, allowing them to modify the app globally. Plugin structure: // myPlugin.js export const MyPlugin = { install(app, options) { // Add global component app.component("MyButton", MyButton); // Add global directive app.directive("tooltip", tooltipDirective); // Add global property app.config.globalProperties.$myMethod = (text) => doSomething(text); // Provide global data (inject anywhere) app.provide("myService", new MyService(options)); // Inject mixin (use sparingly) app.mixin({ mounted() { /* ... */ } }); console.log("Plugin installed with options:", options); } };. Installing a plugin: app.use(MyPlugin, { apiKey: "abc123", timeout: 5000 }). Function plugin: if the plugin is just a function with an install method: app.use((app, options) => { app.component("Icon", Icon); }). Idempotency: app.use() prevents the same plugin from being installed twice. Well-known plugins: Vue Router (provides useRouter, useRoute via provide/inject), Pinia (provides usePinia), Vue i18n, Vue Test Utils. Creating testable plugins: use provide/inject for services instead of globalProperties — easier to mock in tests. Plugin typing (TypeScript): augment module declarations for globalProperties: declare module "vue" { interface ComponentCustomProperties { $myMethod: (text: string) => void; } }. Plugin best practices: avoid modifying Vue itself (Vue.prototype patterns); use app-scoped changes; document the provide keys.

Open this question on its own page
06

What is Vue 3 script setup deep dive?

<script setup> is compile-time syntactic sugar for Composition API that reduces boilerplate. Deep understanding: Compiler transformations: <script setup> is compiled into a setup(props, ctx) function. Top-level variables become the setup() return value (automatically exposed to template). Imports are preserved at module scope. Top-level await: const data = await fetchData() at top level creates an async setup component that participates in Suspense boundaries. Compiler macros: defineProps, defineEmits, defineExpose, defineOptions, defineModel, withDefaults — these are NOT imported functions; they are compile-time macros that are removed during compilation and replaced with the appropriate setup() code. defineOptions (Vue 3.3+): set component options not expressible in script setup: defineOptions({ name: "MyComponent", inheritAttrs: false });. defineModel (Vue 3.4+): creates a two-way binding signal: const count = defineModel<number>({ default: 0 }); count.value++; — compiles to modelValue prop + update:modelValue emit. useAttrs / useSlots / useTemplateRef (Vue 3.5+): reactive access to $attrs and $slots inside setup. Generic components (TypeScript): <script setup lang="ts" generic="T"> defineProps<{ items: T[] }>(); </script>. Module exports: named exports in <script setup> are module-level (not component instance level); const/function/class are instance-level (returned by setup). To export module-level: use a separate <script> block alongside <script setup>.

Open this question on its own page
Back to All Topics 51 questions total