All jobs / Frontend / web developer
Vue interview questions
Vue questions a Frontend / web developer can be asked, from first job to senior. Open one to see the strongest answer and why it works.
A child component receives a quantity prop and has plus and minus buttons.
In Vue, how should the child change the quantity?
The strongest answer: It must not mutate the prop. It emits an event with the new value and the parent updates its own state — or the pair is wired with v-model, which is the same prop-plus-event contract
Why it works: Right — one-way data flow: props down, events up. v-model on a component is sugar for exactly that.
- Props down, events up.
- Never mutate a prop.
- Emit the new value.
- v-model is prop plus event.
In a Vue 3 component using script setup, you try to focus an input through a template ref at the top of setup and it is null. Why, and where does the code go?
The strongest answer: setup runs before the component is rendered, so the element does not exist yet. DOM access belongs in onMounted, when the template ref has been filled
Why it works: Exactly — setup is before render; onMounted is the first hook where the DOM and template refs exist.
- No DOM before mount.
- setup runs before render.
- Touch elements in onMounted.
- Clean up in onUnmounted.
In setup, a developer writes const { count } = state, where state is a reactive object. The template shows count, but it never updates.
What went wrong, and what are the fixes?
The strongest answer: Destructuring copied the current number out of the reactive proxy, so the link is lost. Read state.count directly, use toRefs to destructure into refs, or hold the value in a ref from the start
Why it works: Right — reactivity lives on property access through the proxy. A destructured primitive is a plain copy; toRefs keeps the connection.
- Reactivity lives on the access.
- Destructured primitives are snapshots.
- Use toRefs or state.count.
- Stores: destructure with storeToRefs.
In Vue, when do you use a computed property and when a watcher?
The strongest answer: computed for values derived from other state — cached until a dependency changes; watch for side effects such as a request or saving a draft. Derived state kept in sync by a watcher is a smell
Why it works: Exactly — derive with computed, react with watch. A watcher that only copies state into other state is a computed waiting to be written.
- Derive with computed, react with watch.
- computed: cached, pure, returns a value.
- watch: side effects on change.
- No fetches during render.
A user page loads its data in onMounted. Clicking from /users/1 to /users/2 changes the URL, but the page still shows user 1.
Why, and how do you fix it?
The strongest answer: Vue Router reuses the same component instance when only the param changes, so onMounted does not run again. Watch the route param — or take it as a prop and watch that — and refetch
Why it works: Right — same route record, same instance, no remount. React to the param instead of relying on the mount hook.
- Same component, new params: no remount.
- Don’t rely on onMounted for params.
- Watch the param or the prop.
- Cancel the stale request too.
A useWindowSize composable adds a resize listener when it is called. After an hour of navigating, memory and handler counts keep growing.
What is wrong with the composable, and what is the contract a composable should follow?
The strongest answer: It never removes the listener. Register in onMounted and remove in onUnmounted inside the composable, call it synchronously in setup so those hooks bind to the component, and return refs
Why it works: Exactly — a composable owns its side effects: set up and tear down inside, tied to the caller’s lifecycle, state handed back as refs.
- A composable cleans up after itself.
- Add listeners in onMounted.
- Remove them in onUnmounted.
- Call composables synchronously in setup.
A Vue 3 app has one Pinia store holding everything: the logged-in user, every form’s field values, modal open flags and cached API lists.
How do you restructure state, and what rule do you give the team?
The strongest answer: Several stores split by domain. Pinia keeps state that is genuinely shared or must outlive a page; form fields and modal flags go back to local component state; changes go through actions so they are traceable
Why it works: Right — the question for every piece of state is who else needs it and for how long. Global by default makes every component depend on everything.
- Global only when it is shared.
- Stores by domain, not one bucket.
- Local state for forms and toggles.
- Mutate through actions.
A five-year-old Vue codebase is on Vue 3 but written entirely in the Options API with mixins and Vuex. Some engineers want a full rewrite to the Composition API and Pinia.
What is your call?
The strongest answer: No rewrite. Both APIs are supported, so new code uses Composition API; mixins become composables when their components are touched; Vuex moves to Pinia module by module; effort goes where change is frequent
Why it works: Exactly — the Options API is not deprecated, so the migration is a choice, not an emergency. Spend it where mixins hurt and the code changes often.
- Migrate by value, not by fashion.
- New code: Composition API and Pinia.
- Mixins to composables when touched.
- Separate refactor pull requests from features.