Skip to content
>_ StartupJobList_

Interview questions · Worked answers

Vue 3 Interview Questions: 10 Worked Answers

Follow reactive reads to their effects, keep ownership explicit, and handle cleanup at the lifetime that starts the work.

Intermediate Vue 3 developers · Vue 3 Composition API and standard reactivity. Vue 2 and framework-specific conventions are outside scope; version-specific watcher APIs are named.

When does a ref need `.value`?

In JavaScript setup code, a ref is an object whose reactive value is stored in .value. Vue templates unwrap refs in common top-level template expressions, so the syntax differs between script and template. The ref wrapper gives Vue a property access it can track when the contained primitive changes.

A frequent bug is reading count as if it were the number in setup code, or forgetting .value when passing it to a plain function. Template unwrapping has edge cases, especially nested refs in ordinary objects; do not assume every JavaScript context unwraps identically.

JS · Worked example
import { ref } from 'vue';
const count = ref(0);
count.value += 1;

In setup JavaScript, count.value reads or updates the number; in the template’s top-level binding, {{ count }} unwraps it. Passing the ref to a plain function passes the ref object unless the code explicitly reads .value.

Illustrative, not executed.

Follow-up: Should I use ref for every value?

Use a ref when a value needs to be reactive and replaced as a whole. reactive can suit a stable object whose properties are mutated; choose the representation that keeps ownership clear.

Read the source: Vue Reactivity Fundamentals · Vue Reactivity API

↑ Back to questions

How do reactive reads become dependencies?

Vue's reactive proxy intercepts reads and writes to track which effects depend on which properties. A ref similarly exposes a tracked .value access. When a tracked value changes, Vue schedules dependent rendering or effects.

A dependency trace is: getter reads unit and quantity; Vue tracks those reads; assignment to quantity invalidates the computed value; next access recomputes. Computed values are lazy and cached until dependencies change.

JS · Worked example
import { computed, reactive } from 'vue';

const cart = reactive({ unit: 12, quantity: 2 });
const total = computed(() => cart.unit * cart.quantity);
console.log(total.value); // 24
cart.quantity = 3;
console.log(total.value); // 36

The first read returns 24 from 12 × 2 and tracks both property reads. Changing quantity invalidates the cached result; the next access recomputes 36, and another access without a dependency change reuses that value.

Illustrative, not executed.

Follow-up: Does Vue track any variable I read?

No. It tracks reactive proxy/ref accesses inside the relevant effect; ordinary local variables are not reactive on their own.

Read the source: Vue Reactivity in Depth · Reactivity Fundamentals

↑ Back to questions

When should a value be computed or watched?

Use computed for a pure value derived from reactive state. Vue caches it until its tracked dependencies change. Use watch when a specific source change should trigger a side effect, such as saving a preference or loading data. A computed getter should not perform network requests or mutate other state.

A watcher has an explicit source and callback. watchEffect instead tracks reactive values read synchronously during its execution. Pick the trigger semantics you need; do not substitute one for the other because one looks shorter.

JS · Worked example
import { computed, ref } from 'vue';

const quantity = ref(2);
const unitPrice = ref(8);
const total = computed(() => quantity.value * unitPrice.value);

total is a cached value derived from the two refs; reading total.value gives 16. Use watch when a selected source change should trigger a network request or another side effect, rather than putting that work in the computed getter.

Illustrative, not executed.

Follow-up: Can a watcher maintain a duplicated total field?

It can, but that creates avoidable synchronization state when computed can derive the total directly.

Read the source: Computed Properties · Watchers

↑ Back to questions

What does watcher flush timing change?

A watcher callback runs according to Vue's scheduler. The default timing is before the owning component's DOM update; a post-flush watcher is appropriate when it must inspect the updated DOM. A synchronous watcher runs immediately and bypasses batching, so it can run repeatedly during a burst of synchronous mutations.

If code reads an element's new size, use the documented post-flush mechanism instead of assuming the DOM already reflects a state change. If code merely computes a value, a watcher is likely the wrong tool. Timing options are behavioral contracts and should be covered by a targeted component test.

Follow-up: Does nextTick wait for network work?

No. It waits for Vue's pending DOM update cycle, not arbitrary promises or I/O. Await the request separately and then use the scheduler tool if the DOM update must be observed.

Read the source: Vue Watchers: Callback Flush Timing · Vue nextTick

↑ Back to questions

How do you stop stale async results from replacing fresh ones?

Watch the query that owns the request, register cleanup, and prevent a prior result from publishing after a newer query starts. Cancellation can save work; a generation check protects correctness if cancellation loses a race or the operation cannot be cancelled.

This shows the policy, but production code also handles abort errors and request failures. Vue 3.5+ provides onWatcherCleanup(), which must be called synchronously before an await; the callback's onCleanup argument is an alternative registration route. Do not put cleanup registration after a suspension point and assume it applies retroactively.

JS · Worked example
let generation = 0;
watch(query, async (value, _oldValue, onCleanup) => {
  const current = ++generation;
  const controller = new AbortController();
  onCleanup(() => {
    controller.abort();
    if (current === generation) generation++;
  });
  const result = await fetchResults(value, { signal: controller.signal });
  if (current === generation) results.value = result;
});

When the query changes, cleanup invalidates the earlier generation; if request A resolves after request B, A fails the generation check and cannot replace B’s result. Cancellation is an optimization, while the publication guard enforces correctness.

Illustrative, not executed.

Follow-up: Does abort guarantee the server did no work?

No. It can stop client-side waiting or transmission when supported, but the server may already have received and processed the request. Use idempotency and server-side cancellation semantics if the operation mutates data.

Read the source: Vue Watcher Cleanup · Vue reactivity API

↑ Back to questions

Why shouldn't a child assign to a prop?

Props flow from parent to child. The child should emit an event describing an intended change; the owner updates the source value and passes the new prop down. Mutating a prop directly obscures ownership and can be overwritten when the parent renders again.

When a child needs a local draft, copy the prop deliberately and define when that draft resets or syncs. A local copy is a separate state policy, not a loophole for silently changing parent-owned data.

vue · Worked example
<script setup>
const props = defineProps({ page: { type: Number, required: true } });
const emit = defineEmits(['update:page']);
function next() { emit('update:page', props.page + 1); }
</script>

The child emits the requested next value, and the parent decides whether to update the source prop. required: true documents that the caller must supply page; Vue's Number prop check warns about an invalid value in development but does not coerce or enforce it in production. Validate untrusted values before arithmetic.

Illustrative, not executed.

Follow-up: Is v-model an exception?

It is a convention that connects a prop and update event. Ownership still flows through the parent binding; it does not make mutation responsibility disappear.

Read the source: Vue Props · Component Events

↑ Back to questions

What belongs in a composable?

A composable is a function that packages reusable stateful Composition API logic. Values created inside the composable are normally per-call state. Module-level refs are shared singletons, which may be intentional but can leak state between component instances or server requests if used carelessly.

A useSearch() composable might own a query ref, request status, cleanup and a result list. It should not hide unrelated global configuration or make an undocumented shared cache. Keep the public return shape small and explain whether returned refs are mutable by callers.

Follow-up: Should every component extract a composable?

No. Extract when behavior is reused or the component becomes clearer with an explicit owner. One-off state can remain local.

Read the source: Vue Composables · Vue SSR guidance

↑ Back to questions

Why does `v-for` need a stable key?

A key tells Vue which rendered node corresponds to which item when a list changes. Without a stable key, Vue can patch nodes by position and reuse local DOM/component state for the wrong record. Use a stable unique identifier for reorderable rows.

The key should identify an item among siblings and should remain stable across updates. An array index is unsafe when rows can be inserted, removed or reordered. If each rendered row has no state or temporary DOM state and the list is truly static, index behavior may be acceptable, but stable IDs remain clearer.

vue · Worked example
<Row v-for="item in items" :key="item.id" :item="item" />

When an item is inserted before another row, an index key can cause Vue to reuse the old row instance for a different item. A stable record ID lets component and DOM state follow the intended item through reordering.

Illustrative, not executed.

Follow-up: Does a key force a full rerender?

It participates in identity comparison. Keeping the same key preserves identity where the virtual DOM can reuse it; changing it deliberately replaces that subtree and resets its local state.

Read the source: Vue List Rendering · Vue key special attribute

↑ Back to questions

Which lifecycle hook is safe for browser-only work?

onMounted runs after the component is mounted in the client, so it is suitable for accessing its DOM or registering a browser-only listener. Pair the work with cleanup in onUnmounted. These hooks do not run during server-side rendering, so server-rendered output cannot depend on work that only happens after mounting.

Guarding browser globals during module initialization is still necessary in SSR because that code can execute before a component mounts. Put browser-only access in client lifecycle code or behind an environment boundary.

JS · Worked example
onMounted(() => {
  window.addEventListener('resize', updateLayout);
});
onUnmounted(() => {
  window.removeEventListener('resize', updateLayout);
});

The mounted callback can read window after client mount, and unmount cleanup removes the listener. SSR may evaluate module setup before mount, so a top-level window reference still throws on the server.

Illustrative, not executed.

Follow-up: Can I fetch initial page data in onMounted for SSR?

That fetch occurs only on the client after hydration and may cause a second loading phase. Use the framework's SSR data mechanism when initial HTML must contain the data.

Read the source: Vue Lifecycle Hooks · Vue SSR

↑ Back to questions

What should a component test observe?

Test the rendered behavior and the boundary contract, not the internal fact that a watcher or computed property exists. For a search component, change the query, control request completion order, and assert that only the newest result appears. For a child prop, assert the emitted update and resulting parent-visible value.

Reactive updates and promises settle on different schedules. Use the test framework's Vue-aware tick helper for DOM updates and explicitly resolve controlled promises for asynchronous work. Avoid arbitrary sleep durations, which couple the test to machine timing instead of the state transition.

Follow-up: Should I test a computed getter directly?

Usually test the observable output that depends on it. A small unit test is reasonable when the derived rule is complex and shared outside the component.

Read the source: Vue Testing Guide

↑ Back to questions

Salary context

Advertised salaries for these skills

These are Hacker News job posts that mention these technologies, not employee earnings or a measure of what a skill adds to pay. The amounts summarize annualized advertised minimums in approximate USD. Groups can overlap and differ in roles, locations and dates.

Vue.js posts

51 posts · 51 usable advertised salary amounts

2024-06-04 to 2026-09-03

$120,000

Median advertised minimum, approximate USD/year

Middle 50%: $90,000–$150,000

Explore Vue.js salaries → Browse matching job posts →
Currency and calculation basis

Approximate USD using ECB daily reference rates as of 2026-10-09. Original currencies and offer ranges remain on the source listings. These figures cover posts tagged Vue.js, rather than every job in the broader technology family.

↑ Back to questions

Keep exploring