Explain props immutability as a production contract: parent ownership, predictable rendering, memoization safety, and the shared-state bugs you debug when children mutate props.
Use this React interview question to rehearse a quick answer, common mistake, follow-up, and production pitfall.
Why are props immutable in React, and what breaks if they aren’t?Frontend interview answer
This React interview question tests whether you can explain React props immutability: parent ownership, memoization, and mutation bugs, connect it to production trade-offs, and handle common follow-up questions.
- React props immutability: parent ownership, memoization, and mutation bugs explanation without falling back to memorized docs wording
- Props and Immutability reasoning, edge cases, and production failure modes
- How you would answer the most likely React interview follow-up
Core idea
Props are inputs owned by the parent. React’s mental model is: UI = f(props, state, context). If a child mutates those inputs, rendering stops being a pure computation and you get hidden side effects, broken memoization assumptions, and shared mutable state bugs that are painful to debug in production.
Why props are read-only | What React assumes | What you gain |
|---|---|---|
Parent owns the data | Only the parent decides when/why props change | Clear ownership + easier reasoning |
Render should be pure | Same inputs => same output | Safe re-renders, retries, and dev StrictMode stress tests |
Optimizations rely on identity | Shallow comparisons (memo/PureComponent) are meaningful | Skipping work becomes correct and predictable |
What breaks if you mutate props
JavaScript won’t stop you from mutating objects/arrays passed via props, but React will still treat props as if they were immutable. That mismatch causes bugs.
Breakage | What it looks like | Why it happens |
|---|---|---|
Missed re-renders / stale UI | You change a field, but UI doesn’t update (or updates inconsistently) | Parent didn’t produce a new reference; memo/shallow compare thinks “nothing changed” |
Shared mutable state bugs | One child “mysteriously” affects another sibling | They both reference the same mutated object/array |
Invalid assumptions in concurrent rendering | Weird flickers, order-dependent bugs, “it depends on timing” | React may render, pause, retry; mutations during render leak across attempts |
Debugging becomes painful | You can’t trace where the data changed | Mutation hides the real source of change (no single writer) |
import React from 'react';
const UserCard = React.memo(function UserCard({ user }) {
// ❌ Mutating a prop object
user.lastSeenAt = Date.now();
return (
<div>
<div>{user.name}</div>
<div>lastSeenAt: {user.lastSeenAt}</div>
</div>
);
});
export default function App() {
const [user, setUser] = React.useState({ id: 1, name: 'Ada', lastSeenAt: 0 });
// Parent does NOT create a new object unless setUser is called.
// UserCard is memoized; shallow compare sees same `user` reference and may skip re-render.
return (
<>
<UserCard user={user} />
<button onClick={() => setUser((u) => ({ ...u, name: u.name + '!' }))}>
Update name (new object)
</button>
</>
);
}
Memoized child + mutated prop object
The subtle bug is not just “mutation is bad.” It is that React may keep the same reference, so memoized children and sibling readers observe partially updated shared state at surprising times.
const SettingsPanel = React.memo(function SettingsPanel({ settings }) {
settings.sort = 'name'; // ❌ mutate parent-owned object in place
return <div>{settings.sort}</div>;
});
// Parent still holds the same settings object reference,
// so memo/shallow comparison can miss the real change boundary.
Another classic footgun: mutating arrays
Sorting, pushing, splicing, or reversing a prop array mutates in-place and can corrupt parent/sibling views.
function List({ items }) {
// ❌ Mutates parent-owned array in-place
items.sort((a, b) => a.label.localeCompare(b.label));
return <ul>{items.map((x) => <li key={x.id}>{x.label}</li>)}</ul>;
}
// ✅ Make a copy
function ListSafe({ items }) {
const sorted = [...items].sort((a, b) => a.label.localeCompare(b.label));
return <ul>{sorted.map((x) => <li key={x.id}>{x.label}</li>)}</ul>;
}
// ✅ Copy nested structures too when a child needs a transformed version
function Filters({ options }) {
const nextOptions = {
...options,
groups: options.groups.map((group) => ({
...group,
items: [...group.items].sort((a, b) => a.label.localeCompare(b.label))
}))
};
return <Sidebar options={nextOptions} />;
}
Important clarification
“Props are immutable” is a React rendering contract, not a JavaScript language rule. JavaScript will let you mutate the object. React code becomes unpredictable when you do.
If you need to “change props”… | Do this instead |
|---|---|
Child needs to request an update | Pass a callback prop (events up), parent updates state and passes new props down |
Need derived data | Compute immutably (copy + transform) inside render or memoize with useMemo if expensive |
Need local editable state | Initialize local state from props carefully (and handle updates explicitly) |
Props are immutable because React’s rendering model assumes components are pure functions of their inputs, and because performance optimizations depend on stable identity + parent ownership. If you mutate props, you introduce hidden side effects: missed updates, cross-component contamination, and timing-sensitive bugs—especially with memoization and concurrent rendering.
Use this as one explanation rep, then continue with the React interview questions cluster or a guided prep path.