Articles
Vue.js Interview Questions for Frontend Engineers: Reactivity, Composition API, State, and Performance
Prepare for Vue.js interview questions on reactivity, Composition API, component state, Pinia, watchers, SSR, hydration, testing, and performance.
Vue.js interview questions for frontend engineers test whether you can explain how dependencies are tracked, where state belongs, how components communicate, and how a measured performance problem reaches the DOM. Strong answers connect a Vue API to ownership, timing, cleanup, and a user-visible result instead of listing syntax.
Vue interview answer frame: Name the reactive source, tracked read, update boundary, side-effect owner, component contract, and performance evidence. This guide covers Vue-specific decisions; it does not repeat general JavaScript, browser-rendering, or CSS fundamentals.
What Vue interviewers are actually testing
A Vue interview is usually an ownership and prediction test. The interviewer wants to know whether you can trace a state change through Vue's reactivity system, keep component contracts explicit, and debug the correct layer.
| Area | Baseline answer | Strong frontend signal |
|---|---|---|
| Reactivity | Knows ref() and reactive() | Predicts which read is tracked, which write triggers, and where the connection can be lost |
| Composition API | Uses the SFC setup syntax | Groups logic by feature, exposes a narrow contract, and owns cleanup |
| Components | Passes props and emits events | Preserves one-way data flow and chooses an intentional v-model or injection boundary |
| State | Reaches for Pinia | Distinguishes local, shared, server, and URL state before adding a store |
| Performance | Suggests v-memo | Profiles first, stabilizes inputs, changes one measured cause, and remeasures |
As of September 1, 2026, the npm registry record for Vue lists 3.5.42 as the stable latest release; Vue 3.6 release candidates also exist. State the employer's version before relying on 3.5 behavior such as reactive props destructuring or onWatcherCleanup().
How does Vue 3 reactivity work?
Vue tracks reactive reads at runtime and triggers subscribed effects when those values change. The reactivity fundamentals explain the two main mechanisms: reactive() wraps objects with a JavaScript Proxy, while a ref exposes tracked getter and setter behavior through .value in JavaScript. During a component render, reading a ref or reactive property registers the render effect as a dependency.
That model gives you a practical debugging chain:
- Find the source value and verify that application code uses the proxy or ref, not an untouched raw object.
- Find the tracked read inside the render, computed value, watcher, or effect.
- Identify the write and whether it changes the dependency that was actually read.
- Await
nextTick()only when code must observe the DOM after Vue's buffered update.
Deep reactivity is convenient but not free. A ref holding an object converts it deeply through reactive(). For a large immutable payload, shallowRef() can keep only .value reactive, making replacement the explicit update boundary.
When should you use ref() versus reactive()?
Use ref() as the default source primitive: it accepts primitives and objects, can be replaced as a whole, and can be passed through composables without losing its identity. Use reactive() when one stable object represents cohesive mutable state and property-level mutation is natural.
reactive() has three interview-relevant limits. It only accepts object types; replacing the proxy variable disconnects existing consumers; and destructuring a primitive property creates a plain value. toRefs() or a getter can preserve a property connection when an API truly needs destructuring.
const filters = reactive({ query: '', onlyOpen: false })
const { query } = filters // plain string: disconnected
const { query: queryRef } = toRefs(filters) // connected ref
Do not reduce the answer to “objects use reactive, primitives use ref.” A ref can own an object, and replacement semantics often make it clearer for fetched data, undo state, or immutable updates.
What is the difference between computed(), watch(), and watchEffect()?
computed() creates cached derived state. Its getter should be pure: read reactive inputs and return a value without writing other state, starting requests, or mutating the DOM. Vue reevaluates it when a tracked dependency changes and can avoid downstream work when the computed result remains stable.
watch() and watchEffect() are side-effect tools. watch() makes the source explicit and provides old/new values; watchEffect() discovers dependencies during synchronous execution. In an async watchEffect, values read after the first await are not collected, so hidden dependencies can surprise you.
Use a watcher for an imperative boundary such as a request, browser storage, analytics, or a third-party widget. Register cancellation before stale work completes. In Vue 3.5+, onWatcherCleanup() must be called synchronously; the callback argument remains useful across older versions and after await restrictions.
Why use the Composition API?
The Composition API groups state, derivation, lifecycle, and side effects by feature rather than by option type. That improves extraction into composables, TypeScript inference, and maintenance in components whose concerns cut across data, computed, methods, and lifecycle hooks. Options API remains supported and uses the same underlying system.
A strong composable is not merely a function named useSomething. It owns a focused stateful capability, accepts refs or getters when inputs can change, registers lifecycle cleanup, and returns a narrow contract. Vue's composable guidance commonly returns a plain object containing refs so callers can destructure without breaking reactivity.
Call composables synchronously during setup() or the SFC setup block so Vue can associate watchers and lifecycle hooks with the active component. If a composable starts a timer, subscription, observer, or request, its answer is incomplete until it explains disposal.
How should Vue components communicate?
Start with the smallest explicit boundary.
| Relationship | Preferred contract | Common failure |
|---|---|---|
| Parent to child | Declared, readonly props | Child mutates a nested object owned by the parent |
| Child to parent | Declared emitted event | Child reaches into parent state or relies on an event bus |
| Two-way input | Named component v-model contract | Multiple writers obscure who validates or normalizes |
| Deep stable dependency | Typed provide/inject | Hidden mutation from distant descendants |
| Cross-route/app state | Pinia, URL, or server cache by need | Global store used for temporary component state |
Vue's props guide defines one-way-down bindings. A child cannot replace a prop, but it can still mutate nested properties of an object prop because JavaScript passes the reference. Prefer an emitted intent so the owner performs the mutation. Use component v-model when two-way input is the public contract, and keep transformation or validation rules explicit.
For provide/inject, use symbols in larger applications, provide readonly state when consumers should not mutate it, and colocate mutation functions with the provider. Injection solves distance; it should not hide ownership.
When do you need Pinia?
Use local refs for state owned by one component tree. Use the URL for shareable navigation state, and treat server data as a cache with freshness and error semantics. Add Pinia when multiple distant consumers need coordinated client state, actions, devtools inspection, or application-level lifetime.
Pinia state is created by a function so client and server can receive separate initial state. Getters are computed values over store state, and actions contain operations that may be asynchronous. Avoid destructuring a store directly because it can lose reactive access; use storeToRefs() for state and getters while calling actions from the store.
In SSR, create the application, router, and stores per request. A module-level singleton can leak one user's state into another request. Serialize and hydrate Pinia state with the framework's safe state serializer rather than building markup from unescaped state.
How do you diagnose Vue performance problems?
Separate initial-load cost from update cost before proposing a directive. Vue's performance guide recommends measuring with browser tooling and Vue DevTools, then choosing an optimization that matches the trace.
- Reproduce: Fix the route, device class, data size, and interaction.
- Profile: Check network waterfalls and bundle coverage for loading; use the performance panel and Vue component profiling for updates.
- Trace ownership: Find the unstable prop, expensive computed value, broad reactive object, repeated watcher, or oversized DOM subtree.
- Change one cause: Stabilize props, move pure derivation to
computed, virtualize a large list, code-split a route, or use shallow reactivity for immutable data. - Remeasure: Compare the same scenario and check interaction, memory, accessibility, and loading regressions.
v-once and v-memo are narrow update controls, not default architecture. Extra component abstractions can also cost more than they save inside large lists. Vue's compiler already marks dynamic regions with patch flags and hoists static content, so first prove that application work—not assumed framework overhead—is the bottleneck.
What should you say about SSR, hydration, and testing?
Vue SSR renders HTML on the server and hydrates it on the client by matching components to existing DOM and attaching behavior. It can improve time-to-content and SEO, but adds server cost, universal-code constraints, and hydration risk. Invalid HTML, random values, server/client timezone differences, browser-only APIs, and cross-request state are frequent interview traps.
Test the smallest public contract that proves behavior. The Vue testing guide favors fast unit tests for pure logic and composables, component tests for props/events/rendering, and end-to-end tests for critical user flows. Avoid tests that assert private ref names or implementation details; they make a safe Composition API refactor look like a product regression.
A five-day Vue interview plan
- Day 1: Trace one
ref, one reactive property, one computed value, and one watcher from read to render. - Day 2: Build a composable with a changing input, async cleanup, and an explicit return contract.
- Day 3: Design the same feature with local state, URL state, and Pinia; justify the owner and lifetime.
- Day 4: Implement a typed props/emits or component
v-modelcontract and test observable behavior. - Day 5: Record a performance trace, change one measured cause, and present before-and-after evidence.
Frequently asked questions
Is the Composition API replacing the Options API?
No. Both styles are supported and share Vue's underlying reactivity system. Composition API with Single-File Components is the recommended full-application path because it improves logic reuse, type inference, and organization by feature; Options API can remain clearer for low-complexity or progressively enhanced pages.
Is Pinia required for every Vue application?
No. Start with local refs, props, emits, the URL, and server-data ownership. Add Pinia when coordinated client state crosses distant components or routes and benefits from actions, devtools, plugins, or application-level lifetime.
Why did destructuring break reactivity?
Destructuring a primitive property from a general reactive() object copies its current value. Use the proxy property, a getter, or toRefs(). In Vue 3.5+, destructured props from defineProps() are compiler-transformed within the same SFC setup block, but passing a current value to watch() still requires a getter.
When should I use a watcher instead of a computed value?
Use computed state for pure derivation. Use a watcher when a reactive change must cross into an imperative system such as a request, storage, analytics, or DOM API, and define cleanup, timing, and error behavior.
How should I answer a Vue performance question?
State the user-visible symptom, reproduce it under fixed conditions, profile loading or updates, trace the responsible state and component owner, change one measured cause, and remeasure. Mention trade-offs in bundle size, memory, accessibility, SSR, and maintainability.
Final takeaway
Strong Vue interview answers name the reactive source, tracked read, state owner, component contract, side-effect cleanup, version boundary, measured bottleneck, and verification step.
Sources and Further Reading
- npm registry latest Vue release
- Vue reactivity fundamentals
- Vue computed properties
- Vue watchers
- Vue Composition API FAQ
- Vue composables guide
- Vue props and one-way data flow
- Vue component events
- Vue component
v-model - Vue provide and inject
- Pinia state
- Pinia getters
- Pinia actions
- Pinia SSR
- Vue performance best practices
- Vue rendering mechanism
- Vue testing guide
- Vue server-side rendering guide