Kazi Rahamatullah
Kazi Rahamatullah
AboutProjectsBlogContact
UsesBooks
ResumeView CV
Kazi Rahamatullah

© Copyright 2026 Kazi Rahamatullah

AboutProjectsBlogBooksUses
Twitter/XGitHubProduct HuntCodeSandbox
Back to Blog
VueVue 3Interview PreparationFrontend DevelopmentPinia

Vue Interview Questions

Vue 3 & frontend interview guide — 121 unique questions with theory, examples, and interview-ready answers across Composition API, Pinia, routing, Nuxt, and performance.

Jul 6, 202647 min read

Introduction

This guide merges 121 unique interview questions from 20 preparation notes into one reference — Vue fundamentals, Composition API, reactivity, Pinia, Vue Router, Nuxt, performance, JavaScript, TypeScript, and architecture.

Questions are grouped into 15 topic areas, deduplicated by title, and stripped of company-specific references. Each entry keeps theory, trade-offs where relevant, concise interview answers, and practical code examples.

Quick index

Vue Fundamentals

#Section
1Custom Directives
2Options API vs Composition API
3Single File Components
4Template Syntax & Directives
5Vue 2 vs Vue 3
6Vue vs React
7What is Vue.js?

Composition API & Reactivity

#Section
8Composable vs Mixin
9Composables (Custom Hooks)
10Composables in Conditions?
11Computed vs Watch vs watchEffect
12computed()
13defineExpose()
14nextTick
15reactive()
16Reactivity Internals
17Reactivity System
18Reactivity Utilities
19ref vs reactive
20ref()
21script setup
22toRef / toRefs
23useFetch Composable
24watch vs watchEffect
25watch vs watchEffect vs computed
26watch()
27watchEffect()
28Why Composables?

Components & Templates

#Section
29Components — Props, Emits, Slots
30Dynamic Components
31Emits & Two-Way Communication
32Keys in v-for
33Props & Emits
34Props Validation
35Slots
36Template Refs
37Typed defineProps & defineEmits
38v-if vs v-show
39v-model
40v-model on Components

State Management

#Section
41Auth with Pinia + Router
42Context vs Pinia Performance
43Pinia + API Layer
44Pinia Async Actions
45Pinia Basics
46Pinia defineStore
47Pinia State Management
48Pinia vs Component State
49Pinia vs Vuex
50Pinia Workflow
51Store Composition
52Vuex to Pinia Migration
53Vuex vs Pinia

Routing & Navigation

#Section
54Lazy-Loaded Routes
55Navigation Guards
56Route Configuration
57useRoute & useRouter
58Vue Router
59Vue Router Basics

API & Data Fetching

#Section
60@tanstack/vue-query
61Composition API
62CORS
63Long Polling vs WebSockets
64useAsyncData / useFetch (Nuxt)
65Vue Query
66WebSockets

Performance

#Section
67defineAsyncComponent
68Keep-alive
69Performance — v-memo
70Performance Optimization
71Suspense
72Teleport
73v-memo
74Virtual Scrolling

Nuxt & SSR

#Section
75CSR vs SSR
76Hydration Mismatch
77Nuxt Fullstack
78Nuxt.js & SSR

Architecture & Patterns

#Section
79Feature-Based Structure
80Plugins
81render Functions & h()
82Vue Architecture
83Vue Design Patterns

JavaScript Core

#Section
84Async
85Closure
86Debouncing
87Debouncing & Throttling
88Event Loop
89Garbage Collection

TypeScript

#Section
90TypeScript
91TypeScript with Vue

Security & Accessibility

#Section
92Accessibility

Testing & Tooling

#Section
93Vitest + Vue Test Utils

Behavioral & Soft Skills

#Section
94Behavioral Questions

General & Advanced

#Section
95Auth
96Batching
97Browser Storage
98Computed Properties
99Conditional Rendering
100Counter
101Decision Guide
102Deep vs Shallow Copy
103ES6
104Event Handling & Modifiers
105Flatten without Built-ins
106Flexbox vs Grid
107Grid Toggle Component
108JavaScript Essentials
109Lifecycle Hooks
110Lifecycle Mapping
111Loading / Error / Empty UI
112Merge Objects
113Missing Numbers
114Provide / Inject
115Realtime
116Render vs Patch
117Star Rating (Vue)
118Store Styles
119Vue 3.5+
120Vue Best Practices
121watch vs onMounted Order

Vue Fundamentals

1. Custom Directives

javascript
app.directive('focus', {
  mounted(el) {
    el.focus()
  },
})
// <input v-focus />

Back to index


2. Options API vs Composition API

Theory

Options API — organize code by option type (data, methods, computed, watch).

Composition API — organize code by feature/concern using setup() or &lt;script setup>.

Use Options for simple components or legacy codebases. Use Composition for complex logic, reuse via composables, and TypeScript.

Interview Answer

Options API groups by option type — great for beginners. Composition API groups by feature and enables reusable composables — I default to Composition API in Vue 3 projects.

Real Example

vue
<!-- Options API -->
<script>
export default {
  data() {
    return { count: 0 }
  },
  computed: {
    double() {
      return this.count * 2
    },
  },
  methods: {
    increment() {
      this.count++
    },
  },
}
</script>
 
<!-- Composition API -->
<script setup>
import { ref, computed } from 'vue'
const count = ref(0)
const double = computed(() => count.value * 2)
const increment = () => count.value++
</script>

Back to index


3. Single File Components

Theory

.vue files combine &lt;script>, &lt;template>, &lt;style scoped> — colocated component logic and styling.

Interview Answer

SFCs colocate template, logic, and scoped styles — processed by Vite or Vue CLI.

vue
<script setup>
defineProps({ title: String })
</script>
<template>
  <h2>{{ title }}</h2>
</template>
<style scoped>
h2 {
  color: #42b883;
}
</style>

Back to index


4. Template Syntax & Directives

Theory

DirectivePurpose
v-bind / :Bind attribute
v-on / @Event listener
v-modelTwo-way binding
v-if / v-else / v-showConditional render
v-forList rendering
v-slot / #Named/scoped slots
v-onceRender once, skip updates
v-memoMemoize sub-tree (Vue 3.2+)

v-if vs v-show: v-if removes from DOM; v-show toggles display: none.

Interview Answer

v-if for conditional mount/unmount, v-show for frequent toggles. v-model is syntactic sugar for :value + @update:modelValue. Always use :key with v-for.

Back to index


5. Vue 2 vs Vue 3

Theory

Vue 2Vue 3
ReactivityObject.definePropertyProxy
APIOptions API primaryComposition API + Options
Multiple root nodesNo (single root)Yes (fragments)
TypeScriptAwkwardFirst-class
StateVuex 3/4Pinia (recommended)
Bundle sizeLargerTree-shakeable, smaller
PerformanceGoodFaster diff, better memory

Interview Answer

Vue 3 uses Proxy reactivity — tracks dynamic property add/delete, supports arrays natively, and enables Composition API with better TypeScript and tree-shaking.

Back to index


6. Vue vs React

Theory

VueReact
TypeFramework (progressive)Library (UI only)
TemplateHTML templates + directivesJSX
ReactivityAutomatic (Proxy)Manual (setState/hooks)
StatePinia built-in ecosystemRedux/Zustand external
Learning curveGentlerSteeper (hooks rules)
Two-way bindingv-model nativeControlled components

Interview Answer

Vue gives automatic reactivity and templates with less boilerplate. React gives more flexibility and a larger ecosystem — I pick based on team and project needs.

TopicOne-liner
Vue 3 reactivityProxy — ref for primitives, reactive for objects
Composition APIFeature-based logic, composables for reuse
computed vs watchDerived value vs side effect on change
PiniadefineStore — state, getters, actions, no mutations
v-if vs v-showMount/unmount vs display toggle
provide/injectSkip prop drilling across tree
ComposablesReusable setup logic — useFetch, useDebounce

Back to index


7. What is Vue.js?

Theory

Vue.js is a progressive JavaScript framework for building user interfaces. It combines a reactive data layer with a component-based architecture. "Progressive" means you can adopt it incrementally — drop a script tag on a page or build a full SPA with Vue Router and Pinia.

Pros & Cons

ProsCons
Gentle learning curveTwo API styles (Options + Composition)
Excellent docsSmaller job market vs React in some regions
Built-in reactivityEcosystem split between Vuex/Pinia migration
Single-file components (.vue)Template syntax unfamiliar to JSX devs

Interview Answer

Vue is a progressive framework with reactive data binding and component-based UI. Vue 3 uses Proxy-based reactivity and the Composition API for scalable apps.

Real Example

vue
<script setup>
import { ref } from 'vue'
 
const count = ref(0)
const increment = () => count.value++
</script>
 
<template>
  <button @click="increment">Count: {{ count }}</button>
</template>

Back to index


Composition API & Reactivity

8. Composable vs Mixin

Note

Composables replace mixins — explicit imports, no naming collisions, better TypeScript.

Back to index


9. Composables (Custom Hooks)

Theory

Composables are reusable functions using Composition API — Vue's equivalent of React custom hooks. Must start with use.

Real Example

javascript
// composables/useFetch.js
import { ref, watchEffect, onScopeDispose } from 'vue'
 
export function useFetch(url) {
  const data = ref(null)
  const loading = ref(false)
  const error = ref(null)
  let controller
 
  watchEffect(async (onCleanup) => {
    controller = new AbortController()
    loading.value = true
    error.value = null
    onCleanup(() => controller.abort())
 
    try {
      const res = await fetch(url, { signal: controller.signal })
      if (!res.ok) throw new Error(res.statusText)
      data.value = await res.json()
    } catch (e) {
      if (e.name !== 'AbortError') error.value = e.message
    } finally {
      loading.value = false
    }
  })
 
  return { data, loading, error }
}

Back to index


10. Composables in Conditions?

Note

No — same Rules of Hooks. Composables must run at top level of setup.

javascript
// ❌ if (loggedIn) { const { user } = useAuth(); }
// ✅ const { user } = useAuth(); then use v-if in template

Back to index


11. Computed vs Watch vs watchEffect

Theory

computedwatchwatchEffect
PurposeDerived value (cached)React to specific sourceAuto-track dependencies
ReturnsRefStop handleStop handle
LazyYes — only when accessedNo — on changeImmediate
Use forFiltered lists, totalsAPI calls on ID changeSide effects with auto deps

Interview Answer

computed for derived cached values. watch when I need the old value or explicit control. watchEffect for side effects that auto-track dependencies.

Real Example

vue
<script setup>
import { ref, computed, watch, watchEffect } from 'vue'
 
const query = ref('')
const products = ref([/* ... */])
 
const filtered = computed(() => products.value.filter((p) => p.name.includes(query.value)))
 
watch(query, (newQ, oldQ) => {
  analytics.track('search', { query: newQ, prev: oldQ })
})
 
watchEffect(() => {
  document.title = query.value ? `Search: ${query.value}` : 'Shop'
})
</script>

Back to index


12. computed()

Note

Cached getter; optional setter for writable computed.

javascript
const fullName = computed({
  get: () => `${first.value} ${last.value}`,
  set: (val) => {
    ;[first.value, last.value] = val.split(' ')
  },
})

Back to index


13. defineExpose()

Note

Expose public methods to parent via template ref.

javascript
defineExpose({ focus: () => inputRef.value?.focus() })

Back to index


14. nextTick

Theory

Wait for DOM update after reactive state change before accessing updated DOM.

javascript
count.value++
await nextTick()
console.log(el.value.textContent) // updated DOM

Back to index


15. reactive()

Warning

Proxy-based deep reactivity for objects. Cannot replace entire object reference without losing binding.

Back to index


16. Reactivity Internals

Interview Answer

Vue 3 uses Proxy to trap get/set. track() collects deps on get, trigger() notifies on set. ref wraps value in object with .value getter/setter.

Back to index


17. Reactivity System

Theory

Vue 3 reactivity uses Proxy to intercept get/set on objects.

APIUse for
ref()Primitives — access via .value in JS
reactive()Objects/arrays — no .value
readonly()Prevent mutations
toRef() / toRefs()Destructure reactive without losing reactivity
shallowRef()Only .value change triggers update

Interview Answer

ref wraps primitives with .value; reactive makes objects deeply reactive via Proxy. Destructuring reactive loses reactivity — use toRefs.

Real Example

javascript
import { ref, reactive, toRefs } from 'vue'
 
const count = ref(0)
count.value++ // ✅
 
const state = reactive({ name: 'Amit', age: 28 })
state.name = 'Rahul' // ✅ reactive
 
// ❌ Destructuring breaks reactivity
const { name } = state
 
// ✅ Fix with toRefs
const { name, age } = toRefs(state)
name.value = 'Priya'

Back to index


18. Reactivity Utilities

javascript
import { ref, shallowRef, readonly, toRef, triggerRef } from 'vue'
 
const state = shallowRef({ huge: 'data' }) // only .value change triggers update
const readOnlyState = readonly(state)
const itemRef = toRef(state.value, 'key')
triggerRef(state) // force update for shallowRef

Back to index


19. ref vs reactive

Theory

ref — any value, access .value in script. reactive — objects only, no .value.

Interview Answer

ref for primitives and when replacing entire value. reactive for objects — don't destructure without toRefs.

javascript
const count = ref(0)
const state = reactive({ items: [] })
count.value++
state.items.push(item)

Back to index


20. ref()

Note

Wraps value in reactive object — .value in script, auto-unwrap in template.

Back to index


21. script setup

vue
<script setup>
// Top-level bindings auto-exposed to template
// defineProps, defineEmits, defineExpose are compiler macros
const props = defineProps({ id: String })
const emit = defineEmits(['save'])
</script>

Note

No need to return from setup — less boilerplate than setup() function.

Back to index


22. toRef / toRefs

Note

Preserve reactivity when destructuring reactive objects.

Back to index


23. useFetch Composable

javascript
import { ref, watch, toValue } from 'vue'
 
export function useFetch(url, options = {}) {
  const data = ref(null)
  const loading = ref(false)
  const error = ref(null)
  let controller
 
  async function execute() {
    const resolvedUrl = toValue(url)
    if (!resolvedUrl) return
 
    controller?.abort()
    controller = new AbortController()
    loading.value = true
    error.value = null
 
    try {
      const res = await fetch(resolvedUrl, {
        signal: controller.signal,
        ...options.fetchOptions,
      })
      if (!res.ok) throw new Error(`HTTP ${res.status}`)
      data.value = await res.json()
    } catch (e) {
      if (e.name !== 'AbortError') error.value = e.message
    } finally {
      loading.value = false
    }
  }
 
  watch(() => toValue(url), execute, { immediate: true })
 
  return { data, loading, error, refetch: execute }
}
vue
<script setup>
import { useFetch } from '@/composables/useFetch'
 
const userId = ref('1')
const {
  data: user,
  loading,
  error,
  refetch,
} = useFetch(computed(() => `https://jsonplaceholder.typicode.com/users/${userId.value}`))
</script>
 
<template>
  <div v-if="loading">Loading...</div>
  <div v-else-if="error">
    Error: {{ error }}
    <button @click="refetch">Retry</button>
  </div>
  <div v-else-if="user">
    <h1>{{ user.name }}</h1>
  </div>
</template>

Back to index


24. watch vs watchEffect

Interview Answer

watch tracks explicit sources with old/new values. watchEffect auto-tracks deps and runs immediately.

javascript
watch(userId, (id) => fetchUser(id))
watchEffect(() => {
  document.title = user.value?.name ?? 'App'
})

Back to index


25. watch vs watchEffect vs computed

computedwatchwatchEffect
UseDerived valueReact to sourceAuto-tracked side effect
LazyYesNoImmediate

Note

useDeferredValue equivalent in Vue: computed + debounced ref, or @vueuse/core useDebounceFn.

Back to index


26. watch()

javascript
watch(source, (newVal, oldVal) => {}, { immediate: true, deep: true, flush: 'post' })
watch([a, b], ([newA, newB]) => {})

Back to index


27. watchEffect()

Note

Runs immediately, auto-tracks deps. onCleanup for teardown inside effect.

Back to index


28. Why Composables?

Note

Reuse stateful logic across components — Vue's answer to React custom hooks. Explicit, typed, no mixin conflicts.

Back to index


Components & Templates

29. Components — Props, Emits, Slots

Theory

  • Props — parent → child (read-only)
  • Emits — child → parent events
  • Slots — parent injects content into child

defineProps and defineEmits are compiler macros in &lt;script setup>.

Real Example

vue
<!-- Child: UserCard.vue -->
<script setup>
const props = defineProps({
  name: { type: String, required: true },
  role: { type: String, default: 'user' },
})
const emit = defineEmits(['edit', 'delete'])
</script>
 
<template>
  <div class="card">
    <slot name="avatar" />
    <h3>{{ name }}</h3>
    <slot />
    <!-- default slot -->
    <button @click="emit('edit', name)">Edit</button>
  </div>
</template>
 
<!-- Parent -->
<UserCard name="Amit" @edit="handleEdit">
  <template #avatar><img src="/avatar.png" /></template>
  <p>Senior Developer</p>
</UserCard>

Back to index


30. Dynamic Components

Theory

<component :is="activeTab"> switches rendered component. Works with keep-alive.

vue
<component :is="tabs[activeIndex].component" />

Back to index


31. Emits & Two-Way Communication

javascript
const emit = defineEmits(['update:modelValue', 'save'])
emit('update:modelValue', newValue)

Back to index


32. Keys in v-for

Interview Answer

Always use stable unique :key — never index when list can reorder, insert, or delete.

vue
<li v-for="item in todos" :key="item.id">{{ item.text }}</li>

Back to index


33. Props & Emits

Theory

Props flow down (read-only). Emits flow up as events. Use defineProps and defineEmits in script setup.

Interview Answer

Props are read-only parent-to-child data. Emits declare events the child fires to the parent.

vue
<script setup>
const props = defineProps({ label: String })
const emit = defineEmits(['submit'])
</script>
<template>
  <button @click="emit('submit')">{{ label }}</button>
</template>

Back to index


34. Props Validation

javascript
defineProps({
  id: { type: String, required: true },
  status: { type: String, default: 'active', validator: (v) => ['active', 'archived'].includes(v) },
})

Back to index


35. Slots

Theory

Default slot, named slots (#header), scoped slots (child passes data to slot).

vue
<!-- Child -->
<slot name="header" :title="pageTitle" />
<slot :item="row" />
 
<!-- Parent -->
<template #header="{ title }">
  <h1>{{ title }}</h1>
</template>
<template #default="{ item }">
  <span>{{ item.name }}</span>
</template>

Back to index


36. Template Refs

Interview Answer

ref on template element gives DOM access in onMounted — use for focus, scroll, third-party libs.

vue
<script setup>
const inputRef = ref(null)
onMounted(() => inputRef.value.focus())
</script>
<template><input ref="inputRef" /></template>

Back to index


37. Typed defineProps & defineEmits

typescript
const props = defineProps<{ id: string; optional?: number }>()
const emit = defineEmits<{ (e: 'save', payload: FormData): void }>()

Back to index


38. v-if vs v-show

Theory

v-if — conditional mount (lazy, higher toggle cost). v-show — CSS display toggle (initial render cost).

Interview Answer

v-if for infrequent toggles or lazy load. v-show for frequent visibility toggles — stays in DOM.

Back to index


39. v-model

Theory

Two-way binding — :modelValue + @update:modelValue. Can use multiple v-models on one component.

Interview Answer

v-model is sugar for prop + emit. On custom components it's modelValue and update:modelValue.

vue
<!-- Parent -->
<CustomInput v-model="search" />
<!-- Equivalent -->
<CustomInput :modelValue="search" @update:modelValue="search = $event" />
 
<!-- Multiple v-models -->
<UserForm v-model:name="name" v-model:email="email" />

Back to index


40. v-model on Components

Note

Multiple v-models: v-model:title maps to prop title and emit update:title.

Back to index


State Management

41. Auth with Pinia + Router

javascript
export const useAuthStore = defineStore('auth', () => {
  const user = ref(null)
  const token = ref(null)
 
  const isLoggedIn = computed(() => !!token.value)
 
  async function login(credentials) {
    const { data } = await api.post('/auth/login', credentials)
    user.value = data.user
    token.value = data.token
  }
 
  function logout() {
    user.value = null
    token.value = null
  }
 
  return { user, token, isLoggedIn, login, logout }
})

Back to index


42. Context vs Pinia Performance

provide/injectPinia
Re-renderAll injectors on changeStore subscribers only
DevToolsNoYes
UseTheme, i18nCart, auth, complex state

Warning

Don't put frequently changing data in provide/inject — use Pinia with selectors.

Back to index


43. Pinia + API Layer

javascript
// services/api.js
export const api = axios.create({ baseURL: '/api/v1' })
 
// stores/products.js
export const useProductStore = defineStore('products', () => {
  const products = ref([])
  const loading = ref(false)
 
  async function fetchProducts() {
    loading.value = true
    try {
      products.value = (await api.get('/products')).data
    } finally {
      loading.value = false
    }
  }
 
  return { products, loading, fetchProducts }
})

Back to index


44. Pinia Async Actions

javascript
async function fetchProducts() {
  this.loading = true
  try {
    this.products = await api.getProducts()
  } finally {
    this.loading = false
  }
}

Back to index


45. Pinia Basics

Interview Answer

Pinia: defineStore with state, getters, actions. No mutations. useCartStore() in components.

Back to index


46. Pinia defineStore

javascript
export const useCartStore = defineStore('cart', () => {
  const items = ref([])
  const total = computed(() => items.value.reduce((s, i) => s + i.price, 0))
  function addItem(p) {
    items.value.push(p)
  }
  return { items, total, addItem }
})

Back to index


47. Pinia State Management

Theory

Pinia is the official Vue 3 store — simpler than Vuex, no mutations, full TypeScript, DevTools support.

Concepts: defineStore → state, getters, actions.

Interview Answer

Pinia replaces Vuex in Vue 3 — defineStore with state, getters, and actions. No mutations, modular by default, excellent TypeScript and DevTools.

Real Example

javascript
// stores/cart.js
import { defineStore } from 'pinia'
 
export const useCartStore = defineStore('cart', {
  state: () => ({ items: [] }),
  getters: {
    total: (state) => state.items.reduce((sum, i) => sum + i.price, 0),
    itemCount: (state) => state.items.length,
  },
  actions: {
    addItem(product) {
      this.items.push(product)
    },
    async checkout() {
      await api.post('/checkout', this.items)
      this.items = []
    },
  },
})
 
// Component
const cart = useCartStore()
cart.addItem(product)
console.log(cart.total)
javascript
// Setup store syntax (Composition-style)
export const useUserStore = defineStore('user', () => {
  const user = ref(null)
  const isLoggedIn = computed(() => !!user.value)
  async function login(credentials) {
    user.value = await api.login(credentials)
  }
  return { user, isLoggedIn, login }
})

Back to index


48. Pinia vs Component State

Note

Local useState/ref for UI-only. Pinia for cross-route shared state (cart, auth).

Back to index


49. Pinia vs Vuex

Vuex 4Pinia
MutationsRequiredRemoved
ModulesNested modulesFlat stores
TypeScriptBoilerplateNative
DevToolsYesYes
Vue 3SupportedRecommended

Interview Answer

Pinia removes mutations — actions handle sync and async. One store per domain, full TypeScript, modular by default.

javascript
// Vuex (legacy)
mutations: { ADD_ITEM(state, item) { state.items.push(item); } },
actions: { addItem({ commit }, item) { commit("ADD_ITEM", item); } },
 
// Pinia
actions: { addItem(item) { this.items.push(item); } },

Back to index


50. Pinia Workflow

plaintext
User action → store.action() → state update → reactive subscribers re-render
javascript
const cart = useCartStore()
cart.addItem(product) // action mutates state (Immer in Pinia)
// Components using storeToRefs(cart) re-render

Back to index


51. Store Composition

javascript
const user = useUserStore()
const cart = useCartStore()
const checkout = computed(() => ({ user: user.profile, items: cart.items }))

Back to index


52. Vuex to Pinia Migration

Note

Remove mutations — move logic to actions. Modules become separate defineStore calls.

Back to index


53. Vuex vs Pinia

VuexPinia
MutationsRequiredRemoved
ModulesManualAutomatic per store
TypeScriptVerboseNative
Vue 3Vuex 4Recommended

Interview Answer

Pinia is the Vue 3 standard — simpler API, no mutations, better TypeScript. Vuex for legacy only.

Back to index


Routing & Navigation

54. Lazy-Loaded Routes

javascript
{ path: "/admin", component: () => import("./pages/Admin.vue") }

Back to index


55. Navigation Guards

javascript
router.beforeEach(async (to) => {
  if (to.meta.auth && !isLoggedIn()) return '/login'
})

Back to index


56. Route Configuration

javascript
{ path: "/products/:id", name: "product", component: Product, props: true }

Back to index


57. useRoute & useRouter

javascript
const route = useRoute()
const router = useRouter()
router.push({ name: 'product', params: { id: '123' } })

Back to index


58. Vue Router

Theory

Vue Router 4 handles client-side routing for Vue 3.

Key APIs: createRouter, RouterView, RouterLink, useRoute, useRouter, navigation guards.

Real Example

javascript
import { createRouter, createWebHistory } from 'vue-router'
 
const routes = [
  { path: '/', component: () => import('./pages/Home.vue') },
  {
    path: '/products/:id',
    component: () => import('./pages/Product.vue'),
    props: true,
  },
  {
    path: '/dashboard',
    component: () => import('./layouts/Dashboard.vue'),
    meta: { requiresAuth: true },
    children: [{ path: '', component: () => import('./pages/DashboardHome.vue') }],
  },
]
 
const router = createRouter({
  history: createWebHistory(),
  routes,
})
 
router.beforeEach((to, from, next) => {
  const auth = useAuthStore()
  if (to.meta.requiresAuth && !auth.isLoggedIn) next('/login')
  else next()
})

Back to index


59. Vue Router Basics

Theory

createRouter, RouterView, RouterLink, dynamic routes with :id, useRoute(), useRouter().

Interview Answer

Vue Router maps URLs to components — RouterView renders matched component, guards protect routes.

javascript
const routes = [{ path: '/user/:id', component: UserPage }]
const router = createRouter({ history: createWebHistory(), routes })

Back to index


API & Data Fetching

60. @tanstack/vue-query

javascript
const { data, isLoading } = useQuery({
  queryKey: ['products', filters],
  queryFn: () => api.getProducts(filters),
})

Back to index


61. Composition API

vue
<script setup>
import { ref, computed, onMounted } from 'vue'
import { useProductStore } from '@/stores/products'
 
const store = useProductStore()
const filter = ref('')
 
const filtered = computed(() => store.products.filter((p) => p.name.includes(filter.value)))
 
onMounted(() => store.fetchProducts())
</script>

Back to index


62. CORS

Warning

Browser blocks cross-origin reads unless server sends Access-Control-Allow-Origin. Fix on server, not client.

javascript
// Preflight for PUT with JSON
fetch('https://api.example.com/orders', {
  method: 'PUT',
  headers: { 'Content-Type': 'application/json' },
  credentials: 'include',
})

Back to index


63. Long Polling vs WebSockets

Long PollingWebSocket
ConnectionRepeated HTTPPersistent
LatencyHigherLower
UseFallbackLive tracking

Back to index


64. useAsyncData / useFetch (Nuxt)

javascript
const { data, pending } = await useFetch('/api/products')

Back to index


65. Vue Query

javascript
import { useQuery, useMutation, useQueryClient } from '@tanstack/vue-query'
 
const { data, isLoading, error } = useQuery({
  queryKey: ['products', category],
  queryFn: () => api.getProducts(category.value),
})
 
const queryClient = useQueryClient()
const { mutate: addToCart } = useMutation({
  mutationFn: (item) => api.addToCart(item),
  onSuccess: () => queryClient.invalidateQueries({ queryKey: ['cart'] }),
})

Pinia vs Vue Query

PiniaVue Query
DataClient state (UI, cart)Server state (API cache)
CacheManualAutomatic
RefetchManualBackground, staleTime

Back to index


66. WebSockets

javascript
// composables/useWebSocket.js
export function useWebSocket(url) {
  const data = ref(null)
  const status = ref('connecting')
  let ws
 
  onMounted(() => {
    ws = new WebSocket(url)
    ws.onopen = () => (status.value = 'open')
    ws.onmessage = (e) => (data.value = JSON.parse(e.data))
    ws.onclose = () => (status.value = 'closed')
  })
 
  onUnmounted(() => ws?.close())
  return { data, status }
}

Back to index


Performance

67. defineAsyncComponent

javascript
const HeavyChart = defineAsyncComponent({
  loader: () => import('./HeavyChart.vue'),
  loadingComponent: Spinner,
  delay: 200,
})

Back to index


68. Keep-alive

Theory

Cache inactive component instances — preserve state in tab switches.

vue
<keep-alive include="Dashboard,Settings">
  <component :is="view" />
</keep-alive>

Back to index


69. Performance — v-memo

vue
<div v-memo="[item.id, item.selected]">
  <ExpensiveRow :item="item" />
</div>

Back to index


70. Performance Optimization

Theory

TechniquePurpose
v-memoSkip re-render if deps unchanged
keep-aliveCache inactive components
Lazy routesCode splitting
defineAsyncComponentAsync component loading
shallowRef / markRawReduce reactivity overhead
Virtual scrollingLarge lists

Interview Answer

Lazy-load routes and heavy components, use v-memo on expensive lists, keep-alive for tab views, and shallowRef for large non-reactive data.

Back to index


71. Suspense

Theory

Show fallback while async setup or async components resolve.

vue
<Suspense>
  <AsyncDashboard />
  <template #fallback><Spinner /></template>
</Suspense>

Back to index


72. Teleport

Theory

Render content elsewhere in DOM — modals, toasts to <body>.

vue
<Teleport to="body">
  <div class="modal">...</div>
</Teleport>

Back to index


73. v-memo

Note

Skip update if memo deps unchanged — like React.memo for template subtrees.

Back to index


74. Virtual Scrolling

Note

vue-virtual-scroller or @tanstack/vue-virtual for 10k+ row lists.

Back to index


Nuxt & SSR

75. CSR vs SSR

Vue SPA (Vite)Nuxt SSR
RenderClientServer + hydrate
SEONeeds prerenderBuilt-in

Back to index


76. Hydration Mismatch

Warning

Caused by server/client HTML difference — use ClientOnly, match dates, avoid window in setup.

Back to index


77. Nuxt Fullstack

javascript
// server/api/products.get.ts — Nuxt server route
export default defineEventHandler(async () => {
  return await db.products.findMany()
})
 
// pages/products.vue
const { data: products } = await useFetch('/api/products')

Back to index


78. Nuxt.js & SSR

Theory

Nuxt 3 is the Vue meta-framework — file-based routing, SSR/SSG, auto-imports, server API routes.

Rendering: useFetch, useAsyncData for SSR-safe data fetching.

Interview Answer

Nuxt provides SSR, SSG, and file-based routing for Vue. useFetch runs on server and hydrates on client — better SEO than pure SPA.

Back to index


Architecture & Patterns

79. Feature-Based Structure

plaintext
src/features/cart/{components,composables,stores,types}
src/shared/{components,composables}

Back to index


80. Plugins

javascript
app.use(router).use(pinia).use(i18n)

Back to index


81. render Functions & h()

Note

Escape hatch for dynamic rendering — rarely needed with templates.

Back to index


82. Vue Architecture

plaintext
src/features/{auth,catalog,cart}/
  components/, composables/, stores/, api/
src/shared/
src/app/ — router, plugins, App.vue

Back to index


83. Vue Design Patterns

PatternVue equivalent
Custom hooksComposables
Contextprovide/inject, Pinia
HOCComposables or wrapper components
Render propsScoped slots
Compound componentsMulti-component with provide/inject

Interview Answer

Feature folders, composables for logic, Pinia for global state, scoped slots instead of render props, lazy routes for performance.

PatternQ#Core idea
Fundamentals1–5Progressive framework, SFC, directives
Components6–10Props down, emits up, v-model
Reactivity11–16ref, reactive, computed, watch
Composition17–22script setup, composables
State23–26Pinia stores
Router27–30Guards, lazy routes
Async31–34Composables, vue-query
Performance35–38v-memo, keep-alive, async
Advanced39–43Teleport, Suspense
Nuxt44–46SSR, useFetch
TS/Test47–48Typed props, Vitest
Architecture49–50Feature folders, patterns

50 patterns. Master reactivity + Composition API + Pinia and Vue interviews become predictable.

Back to index


JavaScript Core

84. Async

javascript
async function loadDashboard() {
  const [user, orders] = await Promise.all([api.get('/user'), api.get('/orders')])
  return { user: user.data, orders: orders.data }
}

Back to index


85. Closure

Note

Function + remembered scope. Used in composables, debounce, module pattern.

javascript
function createStore(initial) {
  let state = initial
  return {
    get: () => state,
    set: (v) => {
      state = v
    },
  }
}

Back to index


86. Debouncing

javascript
import { ref, watch } from 'vue'
 
export function useDebounce(value, delay = 300) {
  const debounced = ref(value.value)
  let timer
  watch(value, (v) => {
    clearTimeout(timer)
    timer = setTimeout(() => {
      debounced.value = v
    }, delay)
  })
  return debounced
}
vue
<script setup>
const query = ref('')
const debouncedQuery = useDebounce(query, 400)
const { data } = useFetch(computed(() => (debouncedQuery.value ? `/api/search?q=${debouncedQuery.value}` : null)))
</script>
TopicVue approach
Counterref + composable
useFetchwatch + AbortController
Mergereduce + spread / deepMerge
DebounceuseDebounce composable
Share stateprovide/inject or Pinia

Back to index


87. Debouncing & Throttling

javascript
// Vue composable
export function useDebounce(value, delay = 300) {
  const debounced = ref(value.value)
  watch(value, (v) => {
    const t = setTimeout(() => (debounced.value = v), delay)
    onScopeDispose(() => clearTimeout(t))
  })
  return debounced
}

Back to index


88. Event Loop

javascript
console.log('1')
setTimeout(() => console.log('2'), 0)
Promise.resolve().then(() => console.log('3'))
console.log('4')
// 1 → 4 → 3 → 2

Back to index


89. Garbage Collection

Note

Cleanup in onUnmounted — timers, listeners, WebSocket, watch stop handles.

Back to index


TypeScript

90. TypeScript

vue
<script setup lang="ts">
interface Product {
  id: string
  name: string
  price: number
}
 
const props = withDefaults(
  defineProps<{
    product: Product
    editable?: boolean
  }>(),
  { editable: false },
)
 
const emit = defineEmits<{
  save: [product: Product]
  cancel: []
}>()
</script>
typescript
// Typed store
export const useUserStore = defineStore('user', () => {
  const user = ref<User | null>(null)
  const isAdmin = computed(() => user.value?.role === 'admin')
  return { user, isAdmin }
})

Back to index


91. TypeScript with Vue

vue
<script setup lang="ts">
interface Props {
  title: string
  count?: number
}
const props = withDefaults(defineProps<Props>(), { count: 0 })
const emit = defineEmits<{ submit: [id: string] }>()
</script>

Back to index


Security & Accessibility

92. Accessibility

vue
<nav aria-label="Main">
  <button type="button" aria-pressed="true">Active</button>
</nav>

Back to index


Testing & Tooling

93. Vitest + Vue Test Utils

javascript
import { mount } from '@vue/test-utils'
const wrapper = mount(Counter)
await wrapper.find('button').trigger('click')
expect(wrapper.text()).toContain('1')

Back to index


Behavioral & Soft Skills

94. Behavioral Questions

Use STAR method: Situation → Task → Action → Result.

  • Previous project: What you built, stack (Vue 3, Pinia, Nuxt), measurable impact
  • Problem solving: Reproduce → DevTools → fix → verify
  • Collaboration: Code review, disagreeing on Pinia vs provide/inject
RoundTopicOne-liner
1CORSServer Allow-Origin header
1WebSocketPersistent duplex — use composable + onUnmounted cleanup
1Event loopSync → micro → macro
2Grid toggleref 2D array, immutable map toggle
3CultureSTAR stories with metrics

Back to index


General & Advanced

95. Auth

javascript
router.beforeEach(async (to) => {
  const auth = useAuthStore()
  if (to.meta.auth && !auth.token) return '/login'
})

Back to index


96. Batching

Performance

Vue 3 batches reactive updates in same tick — multiple ref changes = one re-render. nextTick waits for DOM flush.

javascript
count.value++
flag.value = true
// One re-render
await nextTick()
// DOM updated

Back to index


97. Browser Storage

APILifetimeUse
localStoragePersistentPreferences
sessionStorageTabWizard state
CookiesConfigurablehttpOnly auth
IndexedDBPersistentOffline cart

Warning

Never store JWT in localStorage — use httpOnly cookies.

Back to index


98. Computed Properties

Theory

Cached derived values — only recompute when dependencies change. Getter-only by default; setter optional.

Interview Answer

computed caches derived state — use instead of methods in templates when result depends on reactive data.

javascript
const firstName = ref('Amit')
const lastName = ref('Shah')
const fullName = computed(() => `${firstName.value} ${lastName.value}`)

Back to index


99. Conditional Rendering

Theory

v-if / v-else-if / v-else, v-show, &lt;template v-if> for grouping without extra DOM.

Real Example

vue
<template>
  <div v-if="loading">Loading...</div>
  <div v-else-if="error">{{ error }}</div>
  <ProductList v-else :items="products" />
</template>

Back to index


100. Counter

Basic

vue
<script setup>
import { ref } from 'vue'
 
const count = ref(0)
const increment = () => count.value++
const decrement = () => count.value--
</script>
 
<template>
  <div>
    <p>Count: {{ count }}</p>
    <button @click="decrement">−</button>
    <button @click="increment">+</button>
  </div>
</template>

Senior — Composable + Bounds

javascript
// composables/useCounter.js
export function useCounter(initial = 0, { min = -Infinity, max = Infinity, step = 1 } = {}) {
  const count = ref(initial)
  const increment = () => {
    count.value = Math.min(count.value + step, max)
  }
  const decrement = () => {
    count.value = Math.max(count.value - step, min)
  }
  const reset = () => {
    count.value = initial
  }
  return { count, increment, decrement, reset }
}
vue
<script setup>
import { useCounter } from '@/composables/useCounter'
const { count, increment, decrement, reset } = useCounter(0, { min: 0, max: 10 })
</script>
 
<template>
  <output aria-live="polite">{{ count }}</output>
  <button :disabled="count <= 0" @click="decrement">−</button>
  <button :disabled="count >= 10" @click="increment">+</button>
  <button @click="reset">Reset</button>
</template>

Back to index


101. Decision Guide

NeedTool
Cart, theme, auth UI statePinia
API product listVue Query
Form inputref/reactive local
URL filtersVue Router query params
Deep tree configprovide/inject

Interview Answer

Server state in Vue Query, client global state in Pinia, local UI in component refs — never duplicate API data in Pinia.

Back to index


102. Deep vs Shallow Copy

javascript
const deep = structuredClone(original)
const shallow = { ...original }

Back to index


103. ES6

Spread, destructuring, optional chaining, nullish coalescing, modules, arrow functions.

Back to index


104. Event Handling & Modifiers

vue
<form @submit.prevent="handleSubmit">
  <input @keyup.enter="search" />
  <a @click.stop.prevent="navigate">Link</a>
</form>

Back to index


105. Flatten without Built-ins

javascript
function flatten(arr) {
  const result = []
  for (const item of arr) {
    if (Array.isArray(item)) result.push(...flatten(item))
    else result.push(item)
  }
  return result
}
TopicVue angle
State workflowPinia actions, not Redux dispatch
RulesComposables at top level — like hooks
SSRNuxt, not Next.js
BatchingVue reactive batch + nextTick

Back to index


106. Flexbox vs Grid

Note

Flex: 1D. Grid: 2D layouts. Same CSS — Vue uses class bindings.

Back to index


107. Grid Toggle Component

Requirements

  • N×N grid, click toggles cell on/off
  • Immutable state updates
  • Clean component structure
vue
<!-- GridToggle.vue -->
<script setup>
import { ref, computed } from 'vue'
 
const props = defineProps({
  rows: { type: Number, default: 3 },
  cols: { type: Number, default: 3 },
})
 
function createGrid(r, c) {
  return Array.from({ length: r }, () => Array(c).fill(false))
}
 
const grid = ref(createGrid(props.rows, props.cols))
 
function toggleCell(row, col) {
  grid.value = grid.value.map((r, ri) => (ri === row ? r.map((cell, ci) => (ci === col ? !cell : cell)) : r))
}
 
function reset() {
  grid.value = createGrid(props.rows, props.cols)
}
 
const activeCount = computed(() => grid.value.flat().filter(Boolean).length)
</script>
 
<template>
  <div class="grid-toggle">
    <header>
      <span>{{ activeCount }} / {{ rows * cols }} active</span>
      <button type="button" @click="reset">Reset</button>
    </header>
 
    <div role="grid" :aria-rowcount="rows" :aria-colcount="cols">
      <div v-for="(row, rowIndex) in grid" :key="rowIndex" role="row" class="grid-row">
        <button
          v-for="(isActive, colIndex) in row"
          :key="colIndex"
          type="button"
          role="gridcell"
          :aria-pressed="isActive"
          :aria-label="`Row ${rowIndex + 1}, Col ${colIndex + 1}, ${isActive ? 'on' : 'off'}`"
          :class="['cell', { 'cell--active': isActive }]"
          @click="toggleCell(rowIndex, colIndex)" />
      </div>
    </div>
  </div>
</template>
 
<style scoped>
.grid-row {
  display: flex;
  gap: 4px;
  margin-bottom: 4px;
}
.cell {
  cursor: pointer;
  border: 2px solid #374151;
  border-radius: 6px;
  background: #f9fafb;
  width: 48px;
  height: 48px;
}
.cell--active {
  border-color: #16a34a;
  background: #22c55e;
}
</style>

Composable Version

javascript
// composables/useGridToggle.js
export function useGridToggle(rows, cols) {
  const grid = ref(createGrid(rows, cols))
 
  const toggleCell = (row, col) => {
    grid.value = grid.value.map((r, ri) => (ri === row ? r.map((c, ci) => (ci === col ? !c : c)) : r))
  }
 
  const activeCount = computed(() => grid.value.flat().filter(Boolean).length)
 
  return { grid, toggleCell, activeCount }
}

Immutable Update — Explain in Interview

javascript
// ❌ Mutates — Vue may not detect if same reference
grid.value[row][col] = !grid.value[row][col]
 
// ✅ New references — always triggers update
grid.value = grid.value.map(/* ... */)

Back to index


108. JavaScript Essentials

Closure: Function + remembered scope — used in composables and debounce.

Event loop: Sync → microtasks (Promise) → macrotask (setTimeout).

javascript
console.log(1)
setTimeout(() => console.log(2), 0)
Promise.resolve().then(() => console.log(3))
console.log(4) // 1,4,3,2

Back to index


109. Lifecycle Hooks

Theory

Options APIComposition APIWhen
beforeCreatesetup()Before instance
createdsetup()After reactive setup
beforeMountonBeforeMountBefore DOM mount
mountedonMountedDOM ready
beforeUpdateonBeforeUpdateBefore re-render
updatedonUpdatedAfter re-render
beforeUnmountonBeforeUnmountBefore destroy
unmountedonUnmountedCleanup

Interview Answer

onMounted for DOM access and API calls. onUnmounted for cleanup — timers, listeners, WebSocket close. setup() replaces beforeCreate/created.

Back to index


110. Lifecycle Mapping

OptionsComposition
mountedonMounted
unmountedonUnmounted

Back to index


111. Loading / Error / Empty UI

vue
<template>
  <Spinner v-if="loading" />
  <ErrorBanner v-else-if="error" @retry="refetch" />
  <EmptyState v-else-if="!items.length" />
  <ItemList v-else :items="items" />
</template>

Back to index


112. Merge Objects

javascript
// Shallow merge — later wins
function mergeObjects(list) {
  return list.reduce((acc, obj) => ({ ...acc, ...obj }), {})
}
 
// Deep merge
function deepMerge(target, source) {
  const result = { ...target }
  for (const key of Object.keys(source)) {
    if (isPlainObject(target[key]) && isPlainObject(source[key])) {
      result[key] = deepMerge(target[key], source[key])
    } else {
      result[key] = source[key]
    }
  }
  return result
}
 
function isPlainObject(v) {
  return v !== null && typeof v === 'object' && !Array.isArray(v)
}
 
function mergeObjectList(list) {
  return list.reduce((acc, obj) => deepMerge(acc, obj), {})
}
vue
<script setup>
import { computed } from 'vue'
 
const configSources = ref([
  { theme: 'dark', api: '/v1' },
  { api: '/v2', timeout: 5000 },
])
 
const mergedConfig = computed(() => configSources.value.reduce((acc, c) => ({ ...acc, ...c }), {}))
</script>

Back to index


113. Missing Numbers

javascript
function findMissing(arr) {
  const set = new Set(arr)
  const missing = []
  for (let i = Math.min(...arr); i <= Math.max(...arr); i++) {
    if (!set.has(i)) missing.push(i)
  }
  return missing
}
findMissing([1, 2, 3, 5, 7]) // [4, 6]

Back to index


114. Provide / Inject

Theory

provide/inject passes data from ancestor to descendant without prop drilling. Symbol keys recommended for large apps.

Unlike props, inject is not explicitly declared in every middle component.

Real Example

javascript
// provider.ts
import { provide, inject, readonly, ref } from 'vue'
 
const ThemeKey = Symbol('theme')
 
export function provideTheme() {
  const theme = ref('light')
  const toggle = () => (theme.value = theme.value === 'light' ? 'dark' : 'light')
  provide(ThemeKey, { theme: readonly(theme), toggle })
}
 
export function useTheme() {
  const ctx = inject(ThemeKey)
  if (!ctx) throw new Error('useTheme requires provider')
  return ctx
}

Back to index


115. Realtime

javascript
const { data: orderStatus } = useWebSocket(`wss://api.example.com/orders/${id}`)
TopicAnswer
Vue fullstackVue/Nuxt frontend + REST/GraphQL + optional Nuxt server routes
StatePinia for client, useFetch/vue-query for server
AuthJWT in httpOnly cookie, router guards, Pinia auth store

Back to index


116. Render vs Patch

Note

Vue render function creates VNode tree. Patch (diff) applies changes to DOM — Vue 3 uses optimized block tree + static hoisting.

Back to index


117. Star Rating (Vue)

vue
<script setup>
const props = defineProps({ max: { type: Number, default: 10 }, modelValue: Number })
const emit = defineEmits(['update:modelValue'])
const hover = ref(0)
const display = computed(() => hover.value || props.modelValue || 0)
</script>
 
<template>
  <div role="radiogroup" :aria-label="`Rating out of ${max}`">
    <button
      v-for="n in max"
      :key="n"
      type="button"
      role="radio"
      :aria-checked="modelValue === n"
      :aria-label="`${n} out of ${max}`"
      @click="emit('update:modelValue', n)"
      @mouseenter="hover = n"
      @mouseleave="hover = 0"
      :style="{ color: n <= display ? '#f59e0b' : '#d1d5db' }">
      ★
    </button>
  </div>
</template>

Back to index


118. Store Styles

javascript
// Options store
export const useCartStore = defineStore('cart', {
  state: () => ({ items: [] }),
  getters: { total: (s) => s.items.reduce((a, i) => a + i.price, 0) },
  actions: {
    addItem(p) {
      this.items.push(p)
    },
  },
})
 
// Setup store (Composition — like script setup)
export const useCartStore = defineStore('cart', () => {
  const items = ref([])
  const total = computed(() => items.value.reduce((a, i) => a + i.price, 0))
  function addItem(p) {
    items.value.push(p)
  }
  return { items, total, addItem }
})

Back to index


119. Vue 3.5+

  • useTemplateRef() — typed template refs
  • Reactive Props Destructure — destructure props reactively
  • SSR improvements in Nuxt 3
vue
<script setup>
const props = defineProps<{ count: number }>();
// Vue 3.5: count stays reactive when destructured
const { count } = props;
</script>
TopicOne-liner
script setupMacros, auto-expose, less boilerplate
shallowRefLarge objects — manual triggerRef
AuthPinia store + router.beforeEach guard
PerformancePinia over provide for hot state

Back to index


120. Vue Best Practices

  • Default to &lt;script setup> + Composition API
  • Colocate composables per feature
  • Pinia for global state, not provide/inject for everything
  • Lazy-load routes and heavy components
  • Use TypeScript for props and emits
  • Scoped styles; CSS modules for complex components

Interview Answer

script setup, composables, Pinia, typed props, lazy routes, and feature-based folders — same principles as React but with Vue idioms.

#TopicOne-liner
1VueProgressive reactive framework
6ref/reactive.value vs Proxy object
5v-modelmodelValue + update:modelValue
11watchExplicit source, old/new value
17PiniadefineStore, no mutations
26Pinia vs VuexPinia for Vue 3
15ComposablesReusable setup logic

Back to index


121. watch vs onMounted Order

Note

onMounted runs after DOM mount. watch with immediate:true runs during setup. watch on ref runs when value changes — after mount if changed in onMounted.

Back to index


Share this article

XLinkedInFacebook
Kazi Rahamatullah

Written by

Kazi Rahamatullah

FullStack Developer

X / TwitterGitHubLinkedIn

Subscribe to my newsletter

Stay up to date and get notified when I share new contents.

No spam ever, unsubscribe anytime