Browse Clojure Foundations for Java Developers

Immutable Data Structures in Clojure

Learn how Clojure's vectors, maps, sets, and lists behave, and how updates create new values without in-place mutation.

Once you accept immutability as the default, the next question is practical:

  • what data structures do I actually use all day?

In Clojure, the answer is usually:

  • vectors
  • maps
  • sets
  • lists

All of them are immutable and persistent, but they are not interchangeable. A Java engineer becomes much more effective once these collections feel like normal tools rather than exotic functional artifacts.

Maps Usually Sit At The Center

Most business data in Clojure is modeled with maps.

1(def order
2  {:order/id 1001
3   :status   :pending
4   :customer {:customer/id 77
5              :email "ada@example.com"}
6   :items    [{:sku "A-1" :qty 2 :unit-price 15M}]})

Why maps show up everywhere:

  • they model named attributes directly
  • they work well with nested domain data
  • they are easy to inspect at the REPL
  • they avoid the ceremony of building tiny classes just to carry data

Typical operations:

1(assoc order :status :paid)
2(dissoc order :temporary/token)
3(update order :items conj {:sku "B-2" :qty 1 :unit-price 9M})
4(assoc-in order [:customer :vip?] true)

Each expression returns a new map-based value. The original order remains available.

Vectors Are The Default Ordered Collection

Vectors are the most common choice when you want an ordered collection of items:

1[{:sku "A-1" :qty 2}
2 {:sku "B-2" :qty 1}]

They are a good fit for:

  • indexed access
  • ordered records
  • appending items
  • returning batches of values

Common operations:

1(conj [:draft :pending] :paid)
2;; => [:draft :pending :paid]
3
4(assoc [:a :b :c] 1 :beta)
5;; => [:a :beta :c]

If you come from Java, think of vectors as the general-purpose “array-list-like” collection, except updates return a new vector instead of mutating the old one.

Sets Model Membership Cleanly

Sets shine when the question is:

  • is this present?
  • what unique values do we have?

Example:

1(def order-flags #{:fraud-review :priority})
2
3(contains? order-flags :priority)
4;; => true
5
6(conj order-flags :gift)
7;; => #{:fraud-review :priority :gift}

This is often clearer than a map of booleans or a list you repeatedly scan for membership.

Lists Exist, But They Are Not Java List

This is an early source of confusion.

Clojure lists are linked lists, not the moral equivalent of java.util.List or ArrayList.

Example:

1'(:validate :price :persist)

Lists are most natural for:

  • code forms
  • sequential processing
  • prepending items at the front

In ordinary application data, you will usually reach for vectors first. Most Java developers over-assume that “list” must be the default collection. In Clojure, that instinct is usually wrong.

These Are Real Immutable Collections, Not Wrappers

This distinction matters.

In Java, an “unmodifiable” collection is often just a wrapper around something that may still be mutable elsewhere.

In Clojure, the collection value itself is immutable. That means:

  • you do not need defensive copying just to trust a caller
  • you can pass values around more freely
  • concurrent readers are simpler to reason about

That is a stronger and more useful guarantee than “please do not call mutating methods.”

Updating Nested Data Feels Different At First

Java code often updates state by reaching into an object and mutating a field.

Clojure updates nested data by returning a new value:

1(def updated-order
2  (update-in order [:items 0 :qty] inc))

That style can feel unfamiliar for a week or two. Then it starts to feel cleaner because the data flow is explicit:

  • here is the old value
  • here is the path I changed
  • here is the new value

No hidden setter calls. No aliasing surprises.

Choosing The Right Collection

Use a:

  • map for named fields and domain entities
  • vector for ordered items and indexed access
  • set for uniqueness and membership
  • list for code-like or prepend-heavy sequence usage

That small decision table is enough for most early Clojure work.

Knowledge Check

### Which Clojure collection is usually the best starting point for modeling named business attributes? - [x] A map - [ ] A list - [ ] A string - [ ] A Java bean > **Explanation:** Maps are the normal way to represent named fields and nested domain data in idiomatic Clojure. ### Why are vectors usually a better default than lists for ordinary application data? - [x] They are the common ordered collection for indexed access and appending - [ ] They are mutable - [ ] They cannot hold maps - [ ] They only work in macros > **Explanation:** Java developers often over-assume that linked lists are the default. In Clojure, vectors are more often the practical general-purpose sequence container. ### What does `(assoc order :status :paid)` do? - [x] It returns a new order value with `:status` updated and leaves the original order unchanged - [ ] It mutates the original order in place - [ ] It only works on vectors - [ ] It removes the `:status` key > **Explanation:** `assoc` produces a new collection value rather than mutating the old one. ### Why are Clojure's immutable collections stronger than Java unmodifiable wrappers? - [x] The collection value itself is immutable, not just exposed through a restricted API - [ ] They prevent all allocations - [ ] They are always faster than mutable collections - [ ] They can only be used in single-threaded code > **Explanation:** An unmodifiable Java wrapper may still hide a mutable collection underneath. Clojure's collection value itself cannot be mutated in place.
Revised on Friday, April 24, 2026