Overview
Immutable data cannot be changed in place; any “update” returns a new value and leaves the original untouched. Default to it for shared state across threads, UI renders, and caches, where in-place mutation causes bugs that appear only under timing pressure. Copy cost is rarely the bottleneck, and reference comparison becomes a cheap change check.
Rules
- Let the language enforce it: Swift
structandlet, Rust bindings, TypeScriptreadonlyandas const. - JavaScript
constandObject.freezeare shallow: nested objects stay mutable. Use spread,structuredClone, or Immer for nested updates, and the non-mutating array methodstoSorted,toReversed,toSpliced, andwith(ES2023). - React relies on new object identities to detect state changes, so never mutate state in place (react-state-management).
- Reach for mutable structures only where measured performance demands it.
// Mutates the caller's array.
function addUser(users: User[], u: User) { users.push(u); return users }
// Leaves the caller's array untouched.
function addUser(users: readonly User[], u: User) { return [...users, u] }Related
- side-effect: mutating shared data is the canonical side effect.
- idempotent: easier to reason about with immutable inputs.
- swift-protocols: Swift’s value semantics.
- react-state-management: immutable updates in React.
- general-principles: the broader principles.