What is the difference between provide/inject and props in Vue?
Learn how Vue provide/inject differs from props, when to use each, how to avoid prop drilling, and how to keep injected data reactive across nested components.
Expected Interview Answer
Props pass data one level down from a parent directly to its immediate child, while provide/inject lets an ancestor supply data to any descendant at any depth without threading it through every component in between.
Props are explicit and traceable: each intermediate component must declare and forward the prop, which becomes tedious across deep trees (prop drilling). Provide/inject creates a dependency-injection channel where an ancestor calls provide() and any descendant calls inject() to read it, skipping the middle layers. Provided values can be made reactive with ref or computed, and injection can supply defaults. Props remain the right choice for direct, well-defined parent-child contracts.
- Avoids prop drilling through many intermediate components
- Decouples deeply nested descendants from the exact tree shape
- Ideal for cross-cutting concerns like themes, locale, or form context
- Keeps immediate parent-child data flow explicit when props are used
- Provided reactive refs propagate updates to all injectors automatically
AI Mentor Explanation
Props are like the captain handing instructions to the fielder standing right next to him, who then relays them on to the next fielder one by one down the line. Provide/inject is the coach broadcasting a single tactical signal from the boundary that any player anywhere on the ground can pick up directly, without it being whispered player to player.
Step-by-Step Explanation
Step 1
Identify the data flow depth
If a child directly needs data from its immediate parent, use props; if a deeply nested descendant needs it, consider provide/inject.
Step 2
Declare props on the child
Use defineProps to declare the expected props with types, then bind them from the parent template.
Step 3
Provide from an ancestor
Call provide('key', value) in the ancestor's setup to expose data to its subtree, wrapping it in ref or computed for reactivity.
Step 4
Inject in a descendant
Call inject('key', defaultValue) in any descendant to read the provided value, supplying a fallback for when no provider exists.
Step 5
Keep mutation controlled
Prefer providing read-only state plus updater functions so descendants request changes rather than mutating shared state directly.
What Interviewer Expects
- Clear grasp of one-way data flow via props
- Understanding of prop drilling and why it becomes painful
- How provide/inject implements dependency injection across depth
- Awareness that provided values need ref/computed for reactivity
- Knowing when each mechanism is the appropriate choice
Common Mistakes
- Claiming provide/inject replaces props for all component communication
- Providing a plain value and expecting reactivity without ref or computed
- Letting many descendants mutate injected state directly, causing untraceable bugs
- Forgetting to supply an inject default and crashing when no provider exists
- Using string keys that collide instead of Symbol or typed InjectionKey
Best Answer (HR Friendly)
“Props hand information straight from a parent component to its direct child, and each layer in between has to pass it along. Provide/inject lets a component higher up share data that any component below it can grab directly, no matter how deep it sits, which saves you from passing the same thing through every level.”
Code Example
// Ancestor.vue
<script setup>
import { provide, ref } from 'vue'
const theme = ref('dark')
provide('theme', theme) // reactive, shared with any descendant
</script>
// DeepChild.vue (any depth below)
<script setup>
import { inject } from 'vue'
const theme = inject('theme', 'light') // 'light' is the default fallback
</script>
<template>
<p>Current theme: {{ theme }}</p>
</template>// Child.vue
<script setup>
const props = defineProps({ label: String })
</script>
<template>
<button>{{ props.label }}</button>
</template>
// Parent.vue
<template>
<Child label="Save" />
</template>Follow-up Questions
- How do you make a provided value reactive and keep descendants in sync?
- What is a Symbol or InjectionKey and why prevent key collisions with it?
- When would you reach for Pinia instead of provide/inject?
- How do you supply a default value in inject and why does it matter?
- Can a descendant mutate provided state, and how do you control that safely?
MCQ Practice
1. Which mechanism best avoids prop drilling through many intermediate components?
provide/inject lets an ancestor share data with any descendant at any depth, skipping the intermediate components that props would require.
2. To make a provided value reactive so injectors update automatically, you should provide a:
Providing a ref or computed keeps the value reactive, so all injecting descendants re-render when it changes; a plain value does not track updates.
3. What happens if a component injects a key that no ancestor provided and no default is given?
Without a matching provider or a supplied default, inject returns undefined, which is why supplying a sensible default is recommended.
Flash Cards
Props direction and scope — One-way, parent to immediate child; each intermediate layer must forward the prop.
provide/inject purpose — Dependency injection: an ancestor shares data with any descendant at any depth without threading it through.
Making provided data reactive — Provide a ref or computed so all injectors stay in sync on change.
inject default — inject('key', fallback) returns the fallback when no ancestor provides the key.
When to prefer props — For explicit, well-defined direct parent-child contracts that should stay traceable.