Skip to content

Immutability Discipline

Definition

If a value can never change after it's created, then no other piece of code — running concurrently or otherwise — can ever observe it in an inconsistent, half-updated state, and no defensive copying is needed to protect it from unexpected modification. Tate calls mutable state "the evil that lurks in the hearts of object-oriented programs," and frames Clojure's answer as the culmination of the book's concurrency arc: where Io, Scala, and Erlang solved concurrency primarily through actors and message passing, "the Clojure approach to concurrency is different. It uses software transactional memory (STM)."

In the Book

The chapter contrasts locking against versioning as two ways databases already handle concurrent access, then shows Clojure applying the database-versioning strategy to in-memory state. A Clojure ref wraps a piece of data that cannot be changed except inside a transaction: (alter movie str ": The Empire Strikes Back") outside a dosync block throws IllegalStateException: No transaction running, but wrapped as (dosync (alter movie str ...)) it succeeds and the reference's new value is visible immediately afterward. Tate is explicit about the payoff: "we know that programs that behave in this manner will absolutely execute correctly, with respect to race conditions and deadlock." He also notes Clojure reserves this machinery for the minority of state that genuinely needs to change — "most of our code will use functional paradigms, and we'll save STM for the problems that could benefit the most from mutability" — treating immutability as the default and mutation as an opt-in, clearly marked exception. The chapter situates this against the rest of the book's languages: Scala's val keyword and immutable collections, and Erlang's single-assignment variables, both push toward the same discipline without STM's transactional guarantees.

Why It Matters

Most concurrency bugs are really bugs about when a piece of shared state can change underneath a reader — immutability doesn't manage that risk more carefully, it removes the risk's precondition entirely, which is why STM, persistent data structures, and "pure by default" functions keep recurring across otherwise very different systems (databases, distributed systems, UI state management) as the concurrency problem gets harder to solve with locks alone. The broader lesson travels past code: any system where multiple actors can read and write a shared record benefits from asking "does this actually need to be mutable, or can I make the default an immutable snapshot and only opt specific fields into controlled, tracked change?"