Immutable Data Structures vs State Based Persistence
Append-only, never-mutate data versus read-modify-write current state. One gives you history and fearless concurrency; the other gives you a smaller disk bill and a dumber mental model. Here's who wins.
The short answer
Immutable Data Structures over State Based Persistence for most cases. Immutability buys you an audit log, time-travel debugging, trivial concurrency, and cache invalidation by reference equality — all for free.
- Pick Immutable Data Structures if need an audit trail, undo/redo, event sourcing, or concurrent writers — anything where 'how did we get here' matters as much as 'where are we'
- Pick State Based Persistence if storing genuinely ephemeral or high-churn state (a cursor position, a cache, a game tick) where history is noise and write amplification is your enemy
- Also consider: They aren't mutually exclusive. Most serious systems use immutable structures in memory and a state-snapshot at the storage layer, or an immutable log compacted into materialized state. Hybrid is the adult answer; pick the default that matches your dominant read pattern.
— Nice Pick, opinionated tool recommendations
What you're actually choosing between
Immutable data structures never change in place: every update returns a new version and the old one stays valid. Think persistent trees (Clojure, Immutable.js), event logs, Git's object store, Datomic. State-based persistence is the default everyone learns first: there's one current value, you read it, you mutate it, you write it back — a row in Postgres, a struct in Redis, a file you overwrite. The fork in the road isn't syntax, it's whether history is a first-class asset or disposable exhaust. Immutability treats the past as data you'll want later; state-based treats it as garbage to overwrite. That single decision cascades into how you handle concurrency, debugging, caching, and audits. Choose it deliberately, because retrofitting history onto a system that bulldozed it is a rewrite, not a patch. Most teams pick state-based by accident and pay for the omission years later.
Where immutable data structures win
Concurrency stops being a knife fight. No in-place mutation means no locks for readers, no torn reads, and structural sharing keeps copies cheap — you share the unchanged 99% and allocate only the diff. You get time-travel debugging for free: every past version is still addressable, so 'reproduce the bug' becomes 'load version 4,812'. Cache invalidation collapses to reference equality — if the pointer didn't change, nothing did, which is why React's whole rendering model leans on it. Audit trails are automatic instead of a bolted-on logging table everyone forgets to write to. The costs are real and you should say them out loud: more allocations, GC pressure, and a disk that grows monotonically unless you compact. But 'my history is too complete' is a luxury problem. 'I overwrote the only copy and can't explain the outage' is a resume problem.
Where state-based persistence earns its keep
It's not a strawman — it's the right call more often than purists admit. When data is high-churn and disposable, immutability is just write amplification with extra steps: a player's position updated 60 times a second, a Redis session counter, a sensor's latest reading. Nobody time-travels a cursor. State-based gives you O(1) reads of 'what is true now' without replaying or materializing a log, a smaller and predictable storage footprint, and a model a new hire understands in one sentence. Databases have spent fifty years optimizing read-modify-write; you're fighting decades of B-tree engineering if you reject it on principle. The honest failure mode is silent history loss and concurrent-write clobbering — last-write-wins quietly eats data. If you can answer 'do I ever need to know the previous value?' with a confident no, state-based isn't the lazy choice, it's the correct one.
The verdict, stated plainly
Default to immutable, demote to state-based only where you can prove history is worthless. The asymmetry is the whole argument: an immutable system that turns out not to need history wastes some disk; a state-based system that turns out to need history requires archaeology you cannot perform because the evidence was overwritten. One mistake costs storage, the other costs the data itself. That's not a close call. The teams who regret immutability complain about GC tuning and compaction jobs — annoying, solvable, well-trodden. The teams who regret state-based persistence are reconstructing what happened from log scraps during a postmortem, and that meeting is never fun. Yes, the pragmatic production answer is usually a hybrid: an immutable log compacted into queryable state. But when you must pick one default to reach for first, reach for the one that keeps your receipts.
Quick Comparison
| Factor | Immutable Data Structures | State Based Persistence |
|---|---|---|
| History & auditability | Every version retained; audit trail is automatic | Only current value; past silently overwritten |
| Concurrency safety | Lock-free reads, structural sharing, no torn reads | Read-modify-write races, last-write-wins clobbering |
| Storage footprint | Grows monotonically; needs compaction/GC | Compact, predictable, in-place |
| Read 'what is true now' | May require replay or materialized view | O(1) direct read of current state |
| Cost of choosing wrong | Wasted disk — annoying, solvable | Lost data — unrecoverable archaeology |
The Verdict
Use Immutable Data Structures if: You need an audit trail, undo/redo, event sourcing, or concurrent writers — anything where 'how did we get here' matters as much as 'where are we'.
Use State Based Persistence if: You're storing genuinely ephemeral or high-churn state (a cursor position, a cache, a game tick) where history is noise and write amplification is your enemy.
Consider: They aren't mutually exclusive. Most serious systems use immutable structures in memory and a state-snapshot at the storage layer, or an immutable log compacted into materialized state. Hybrid is the adult answer; pick the default that matches your dominant read pattern.
Immutability buys you an audit log, time-travel debugging, trivial concurrency, and cache invalidation by reference equality — all for free. State-based persistence is cheaper on disk and easier to explain to a junior, but it throws away your history and makes every concurrent write a fistfight. The expensive thing is the data you lost, not the bytes you saved.
Related Comparisons
Disagree? nice@nicepick.dev