Patternsintermediate
Immutable Update Patterns for State Management
Conventions for updating nested objects and arrays without mutating the original, central to predictable state management in frameworks like Redux and React.
Updating state without mutating it means producing a new object, copying only the path that changed.
Immutable updates with structural sharing are like renovating a single room in a large house by building a new room and updating just that one doorway's connection in the floor plan, while every other untouched room in the house keeps existing exactly as it physically already was, rather than tearing down and rebuilding the entire house from scratch for one room's renovation.
Key Concepts
1
// mutation -- React will not re-render
state.user.name = 'Asha';2
// immutable
const next = { ...state, user: { ...state.user, name: 'Asha' } };3
Why frameworks require this. React and Redux compare by reference to decide what changed:
4
prevState === nextState // if true, nothing re-renders5
Mutating in place keeps the same reference, so the check says nothing changed and the UI does not update.
6
The array patterns.
add [...items, newItem]
prepend [newItem, ...items]
remove items.filter(i => i.id !== id)
update items.map(i => i.id === id ? { ...i, done: true } : i)
insert [...items.slice(0, i), item, ...items.slice(i)]7
And the ones that mutate, which must not be used directly.
push, pop, shift, unshift, splice, sort, reverse8
items.sort() // mutates
[...items].sort() // safe
items.toSorted() // safe, newer9
Nested updates get ugly quickly, which is the honest problem with the pattern:
10
const next = {
...state,
users: {
...state.users,
[id]: {
...state.users[id],
address: { ...state.users[id].address, city: 'Pune' }
}
}
};11
Four levels of spread to change one string, and every level is a chance to forget one — which silently
keeps the old reference and loses the update.
12
Which is why Immer exists.
const next = produce(state, draft => {
draft.users[id].address.city = 'Pune';
});13
You write the mutation; it produces a new object, sharing everything unchanged. Redux Toolkit includes it by
default for this reason.
14
Flat state avoids the problem entirely.
{ usersById: { 1: {...} }, orderIds: [1, 2, 3] }15
Normalised state means updates are one or two levels deep. That is the structural fix, and it is better than
getting good at deep spreading.
16
The cost to be aware of. Copying the path is cheap; copying a large array on every keystroke is not.
Unchanged branches are shared, so the copy is shallow at each level — which is why immutable updates are
affordable at all.
17
What the interviewer is probing.1. "Why does immutability matter for change detection?" Probing: the mechanism. Stalls: "It
is safer." Moves up: a new reference signals a change, so a cheap identity comparison replaces a
deep comparison — which is what memoisation relies on.
18
2. "How do you update a nested property immutably?" Probing: the pattern. Stalls: "Mutate
it." Moves up: spread each level along the path and reuse the untouched branches by reference.
19
3. "What breaks if you mutate state directly?" Probing: the symptom. Stalls: "Nothing."
Moves up: the reference is unchanged, so memoised components and selectors conclude nothing
happened and the UI does not update.
20
4. "What does Immer change?" Probing: the ergonomics. Stalls: "Nothing." Moves up: you
write mutating code against a draft and it produces an immutable result, which removes the
deep-spread noise.