Immutable updates in practice — 'changing' data without mutating it
Immutability sounds like it should mean 'you can never update anything' — in practice it means something more specific: instead of changing a value in place, you create a new value with the change already applied.
Beginner
2 min read
"Updating" without mutating: the actual pattern
The immutability lesson earlier in this domain established why mutable
shared state causes bugs. This lesson is the practical how: what does an
"update" actually look like when you're not allowed to mutate the original?
const user = { name: "Ada", age: 36 };// Mutating (what immutability avoids):user.age = 37; // the ORIGINAL object is changed in place// Immutable update: create a NEW object, copying old fields, overriding oneconst updatedUser = { ...user, age: 37 };// user is untouched: { name: "Ada", age: 36 }// updatedUser is new: { name: "Ada", age: 37 }
The spread syntax (...user) copies every existing field into a brand-new
object, and the field listed afterward (age: 37) overrides the copied
value — user itself is never touched. The same pattern works for arrays:
const numbers = [1, 2, 3];// Mutating:numbers.push(4); // changes the original array// Immutable: create a new array with the item addedconst withFour = [...numbers, 4];// numbers is untouched: [1, 2, 3]// withFour is new: [1, 2, 3, 4]
Why "copy the whole thing" isn't as wasteful as it sounds
A natural worry: doesn't copying an entire object or array for every small
update waste memory? For small, everyday objects (a user record, a form's
state), the cost is negligible — and real functional data structures used
at scale (in languages and libraries built around this pattern) use
structural sharing: nested parts that didn't change are reused by
reference, not actually duplicated, so only the specific path that changed
gets new memory. The mental model "a brand-new full copy every time" is
correct for reasoning about behavior; the actual implementation is
usually smarter about it under the hood.
Nested updates: the part that gets awkward
Updating a deeply nested field immutably means spreading at every level on
the way down:
This nesting is a real, known pain point of manual immutable updates —
exactly why libraries like Immer exist, letting you write code that looks
like ordinary mutation while actually producing a new, immutable value
underneath.
Why this matters day to day
Immutable updates are the mechanism that makes "did this data actually
change?" answerable with a simple reference comparison instead of a deep
inspection — React's rendering optimizations, Redux's state management, and
undo/redo history all depend directly on this pattern being followed
consistently, not just on immutability as an abstract principle.