Skip to content

Immutability — Detailed#

Mutable vs immutable#

classDiagram
  class MutablePoint {
    -x: int
    -y: int
    +setX(int)
    +setY(int)
  }
  class ImmutablePoint {
    +x: int
    +y: int
    +ImmutablePoint(x, y)
    +withX(int) ImmutablePoint
    +withY(int) ImmutablePoint
  }

ImmutablePoint returns a new instance on every "change". Holders of the old one are unaffected.

How to make a class immutable#

  1. All fields final.
  2. No setters.
  3. Don't expose mutable internals (defensive copy on construction + getters).
  4. Don't this-leak during construction.
  5. Make the class itself final (or seal it) so subclasses can't add mutable state.

Java records (since 14) and Kotlin data class val ... give this for free.

Why it matters for concurrency#

sequenceDiagram
  participant T1 as Thread 1
  participant T2 as Thread 2
  participant V as Immutable Value
  T1->>V: read
  T2->>V: read
  Note over T1,T2: no lock needed — value never changes

No race conditions on an object that can't be mutated. This is the easiest concurrency strategy that exists.

Persistent data structures#

For collections, naive copy-on-write is O(N). Persistent / functional data structures share structure under the hood — operations are O(log N) or O(1):

Structure Used by
Persistent vector (32-way trie) Clojure, Scala, Immutable.js
HAMT (hash array mapped trie) Clojure, Scala, .NET ImmutableDictionary
Persistent map / set functional langs everywhere

Where immutability shows up in this site#

  • CRDTs — each replica works with immutable causal histories.
  • Event sourcing — events are immutable; state is a projection.
  • Block & event logs — Kafka, WAL, blockchain — append-only by design.
  • Configuration service / Feature flags — config snapshots passed around the codebase as immutable values.

When not to be immutable#

  • Tight inner loops on huge arrays in performance-critical code (allocation pressure).
  • Hot data structures with high update rates (counters, caches).
  • I/O buffers that must be reused.

Even then, you can localise mutation behind an immutable interface (mutable builder → build → freeze).

Glossary & fundamentals#

Concepts referenced in this design. Each row links to its canonical page; the tag column shows whether it is a high-level (HLD) or low-level (LLD) concept.

Tag Concept What it is Page
HLD Pub/Sub & message brokers topics, consumer groups, delivery semantics pub-sub-pattern
HLD Leader/follower replication sync/semi-sync/async replication, failover replication-leader-follower
HLD LSM vs B-Tree engines WAL, memtable, SSTables, compaction storage-engines-lsm-btree
HLD Event sourcing + CQRS commands -> events; separate read model event-sourcing-cqrs
LLD Data structures & complexity Big-O, common DS, latency numbers data-structures-complexity
LLD Creational patterns Singleton, Factory, Builder, Prototype creational-patterns
LLD Behavioural patterns Strategy, Observer, State, Command, Chain behavioral-patterns
LLD Threading & deadlocks thread states, Coffman, lock ordering threading-and-deadlocks
LLD Immutability immutable types, persistent collections immutability