What breaks if a child mutates a prop directly?

MediumIntermediateVue
Preparing for interviews?

Use guided tracks for structured prep, then practice company-specific question sets when you want targeted interview coverage.

Quick Answer

In Vue, props are meant to be read-only inputs from parent to child. Explain what breaks (correctness + debugging) when a child component directly mutates a prop, including the nested object/array case, and show the correct patterns (emit updates / v-model).

Answer

Core idea

Props are the parent’s state flowing down. If the child mutates a prop, you lose the “single source of truth” and the update path becomes ambiguous (who changed it, when, and why). Vue will warn in dev, but the bigger issue is correctness + maintainability.

What the child does

What breaks

Why it’s bad

Assigns a prop directly (e.g. props.count++)

Vue warns; update is rejected / overwritten on next parent render

Child is trying to mutate parent-owned state without going through the parent

Mutates a nested field on an object/array prop (e.g. props.user.name = 'x')

You silently mutate the parent’s object (shared reference) and create “action at a distance” bugs

Objects/arrays are passed by reference; child mutation becomes parent mutation

Uses a prop as local state (editing it directly)

UI can desync (parent re-renders and resets the child), race conditions with async updates

Parent can re-send the prop anytime; child-local edits aren’t a stable source of truth

Triggers watchers/computed based on mutated prop

Hard-to-trace update loops / unexpected re-renders

You bypass the intended data flow and can create circular updates

What actually breaks when a child mutates a prop

Bad example: child mutates a primitive prop

In Vue 3, defineProps() is shallow readonly. Mutating it causes a dev warning and is considered an anti-pattern.

HTML
<!-- ChildCounter.vue (BAD) -->
<script setup>
const props = defineProps({
  count: { type: Number, required: true }
});

function inc() {
  // ❌ anti-pattern: mutating a prop
  // Vue warns in dev; parent can overwrite on next render.
  props.count++;
}
</script>

<template>
  <button @click="inc">Count: {{ count }}</button>
</template>
                  

Correct pattern: emit an update (parent stays the source of truth)

The child requests a change; the parent applies it.

HTML
<!-- ChildCounter.vue (GOOD) -->
<script setup>
const props = defineProps({
  count: { type: Number, required: true }
});

const emit = defineEmits(['update:count']);

function inc() {
  emit('update:count', props.count + 1);
}
</script>

<template>
  <button @click="inc">Count: {{ count }}</button>
</template>

<!-- Parent.vue -->
<script setup>
import { ref } from 'vue';
import ChildCounter from './ChildCounter.vue';

const count = ref(0);
</script>

<template>
  <!-- v-model:count = :count + @update:count -->
  <ChildCounter v-model:count="count" />
</template>
                  

The tricky case: object/array props

Even if Vue warns, mutating props.user.name is still mutating the same object the parent passed. This couples child behavior to parent state and makes bugs look “random” because the mutation didn’t go through an explicit parent update.

HTML
<!-- ChildUserEditor.vue (BAD) -->
<script setup>
const props = defineProps({
  user: { type: Object, required: true }
});

function rename() {
  // ❌ Mutates parent-owned object via shared reference
  props.user.name = 'New Name';
}
</script>

<template>
  <button @click="rename">Rename</button>
</template>
                  

When you need “editable local state”

Make a local copy for editing, and emit the final value (or emit on each change).

HTML
<!-- ChildNameInput.vue (local draft -> emit) -->
<script setup>
import { ref, watch } from 'vue';

const props = defineProps({
  name: { type: String, required: true }
});
const emit = defineEmits(['update:name']);

const draft = ref(props.name);

// Keep draft in sync if parent changes the prop externally
watch(() => props.name, (v) => { draft.value = v; });

function commit() {
  emit('update:name', draft.value);
}
</script>

<template>
  <input v-model="draft" />
  <button @click="commit">Save</button>
</template>
                  

Interview-ready takeaway

Mutating props breaks one-way data flow: the parent is no longer the single source of truth, updates become non-deterministic to trace, and nested object/array props can mutate parent state by reference. Correct approach: child emits intent (update events / v-model) and the parent owns the actual state mutation.

Similar questions
Guides
12 / 34