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 struct and let, Rust bindings, TypeScript readonly and as const.
  • JavaScript const and Object.freeze are shallow: nested objects stay mutable. Use spread, structuredClone, or Immer for nested updates, and the non-mutating array methods toSorted, toReversed, toSpliced, and with (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] }