Browse Learn Clojure Foundations as a Java Developer

Immutable Data Structures

Understand Clojure's persistent maps, vectors, sets, and lists as practical immutable values that support efficient updates through structural sharing.

Clojure’s default collections are immutable: maps, vectors, sets, and lists do not change in place. Instead, update operations return new values.

For Java engineers, the important point is that immutable does not mean “copy the whole object graph every time.” Clojure collections are persistent data structures.

Persistent collection: an immutable collection that can produce an updated version efficiently by reusing most of the existing structure.

Persistent Does Not Mean Stored On Disk

This term causes early confusion:

Word Meaning here
Persistent collection Old value remains available after an update
Persistent storage Data survives process restart

Clojure’s persistent collections are about value versions and structural sharing, not automatic database persistence.

Updates Return New Values

 1(require '[clojure.string :as str])
 2
 3(def user
 4  {:user/email " A@EXAMPLE.COM "
 5   :user/roles ["admin" "admin"]
 6   :user/active? true})
 7
 8(def normalized-user
 9  (-> user
10      (update :user/email #(str/lower-case (str/trim %)))
11      (update :user/roles set)
12      (assoc :user/source :import)))
13
14user
15;; => {:user/email " A@EXAMPLE.COM ", :user/roles ["admin" "admin"], :user/active? true}
16
17normalized-user
18;; => {:user/email "a@example.com", :user/roles #{"admin"}, :user/active? true, :user/source :import}

The original user remains unchanged. That is the habit to build: describe the next value instead of mutating the current one.

Nested Updates Stay Explicit

 1(def order
 2  {:order/id 42
 3   :order/shipping {:address/city "Toronto"
 4                    :address/postal-code "M5V"}
 5   :order/items [{:item/sku "A" :item/qty 1}
 6                 {:item/sku "B" :item/qty 2}]})
 7
 8(-> order
 9    (assoc-in [:order/shipping :address/city] "Ottawa")
10    (update-in [:order/items 0 :item/qty] inc))

In Java, nested immutable updates often require builders, copy constructors, records, or manual defensive copying. In Clojure, nested value updates are ordinary operations.

Collection Choice Still Matters

Collection Best fit Java comparison
Vector Ordered indexed values Immutable cousin of ArrayList
Map Domain records and lookup tables Immutable cousin of HashMap
Set Membership and uniqueness Immutable cousin of HashSet
List Code forms and head-first sequences Persistent linked-list shape

conj follows the natural shape of the target collection:

1(conj [1 2] 3)
2;; => [1 2 3]
3
4(conj '(1 2) 3)
5;; => (3 1 2)

That is not arbitrary. conj adds where the collection can add efficiently.

Why This Matters In Real Systems

Immutable data helps because:

  • readers can share values safely without defensive copies
  • tests can compare old and new values directly
  • state transitions are visible in return values
  • concurrency bugs around accidental mutation become less likely

You still model change over time. You just put changing identity behind explicit tools such as atoms, refs, agents, database transactions, or queues instead of making every collection mutable by default.

Quiz: Immutable Data

### What does `assoc` return when used on a map? - [x] A new map value with the key added or replaced. - [ ] The same map mutated in place. - [ ] A Java `HashMap`. - [ ] A lazy sequence. > **Explanation:** Clojure maps are immutable. `assoc` returns a new value, usually sharing structure with the old one. ### What is structural sharing? - [x] Reusing most of the old collection internally when creating an updated value. - [ ] Sharing one mutable collection across all threads. - [ ] Saving collections to disk automatically. - [ ] Converting maps into Java beans. > **Explanation:** Structural sharing is what makes persistent immutable collections practical for ordinary application updates. ### What does `(conj '(1 2) 3)` return? - [x] `(3 1 2)` - [ ] `(1 2 3)` - [ ] `[1 2 3]` - [ ] It mutates the list and returns `nil`. > **Explanation:** Lists add efficiently at the front, so `conj` prepends when the target collection is a list.
Revised on Saturday, May 23, 2026