Browse Clojure Foundations for Java Developers

Managing State with Atoms

Use atoms when one identity must change over time, and keep the update function pure so state stays explicit and reviewable.

An atom is what you use after local bindings stop being enough.

That is the simplest way to connect this page to the rest of the chapter:

  • let is for local names during one calculation
  • an atom is for one identity whose value changes over time

If you keep those jobs separate, Clojure state management becomes much easier to reason about.

What An Atom Is For

Officially, atoms are for shared, synchronous, independent state. In practical Java-to-Clojure terms, that means:

Good fit Why an atom works
In-memory cache One current value changes as requests arrive
UI or session state One identity advances from one immutable value to the next
Counters and small registries Updates are independent and should happen now

An atom is not a substitute for:

  • local computation with let
  • coordinated multi-identity transactions
  • durable persistence across processes

Those distinctions matter because many migration problems come from choosing an atom for every changing thing.

The Core Pattern

Start with a pure transition function:

1(defn reserve-seat [show seat-id]
2  (update show :reserved-seats conj seat-id))

Then let the atom own the changing identity:

1(def current-show
2  (atom {:show/id "evening-1"
3         :reserved-seats #{}}))
4
5(swap! current-show reserve-seat "A-12")
6@current-show

This split is the important design move:

  • the function describes the next value
  • the atom installs it as the current value

That is a much stronger model than “I need a mutable variable.”

Why swap! Wants A Pure Function

The official atom reference makes one rule especially important: the function passed to swap! may run more than once because the update can retry under contention.

That means this is bad:

1(swap! current-show
2       (fn [show]
3         (println "reserving seat")  ;; bad: side effect inside retryable update
4         (reserve-seat show "A-12")))

And this is better:

1(println "reserving seat")
2(swap! current-show reserve-seat "A-12")

The atom update stays pure, and the side effect is no longer tied to retry behavior.

reset! Versus swap!

Use the right update tool for the job:

Function Use it when
swap! The next value should be derived from the current value
reset! You already know the exact new value to install

Examples:

1(swap! current-show update :reserved-seats conj "A-13")
2
3(reset! current-show
4        {:show/id "late-show"
5         :reserved-seats #{}})

Most business-state updates should bias toward swap! because they express a transition from the current value.

A Useful Java Comparison

The closest familiar concept is often AtomicReference<T>, but there is an important Clojure difference:

  • Java code often stores mutable objects inside the atomic wrapper
  • Clojure code normally stores immutable maps, vectors, or sets inside the atom

So the atomic part controls identity change, while the data inside still behaves like ordinary immutable values.

That combination is what keeps the code readable under concurrency.

Do Not Replace Every Local With An Atom

If you only need a temporary name during one function call, use let.

If you only need a result from a pure transformation, return the value.

Reach for an atom only when the program truly has one current value that must change over time and be seen consistently by callers.

That is the main discipline this page is trying to teach.

Knowledge Check

### What problem is an atom meant to solve in this chapter? - [x] One identity needs to change over time beyond a single local calculation - [ ] Every temporary step in a function needs its own storage cell - [ ] Multiple identities must update transactionally together - [ ] The program needs durable persistence > **Explanation:** An atom is for explicit in-memory state over time, not for ordinary locals or multi-identity transactions. ### Why should the function passed to `swap!` be free of side effects? - [x] Because `swap!` may retry the function under contention - [ ] Because atoms only store numbers - [ ] Because `swap!` cannot call user functions - [ ] Because side effects are forbidden everywhere in Clojure > **Explanation:** Retry means the function may run more than once. Side effects inside it can therefore happen more than once too. ### When is `reset!` a better fit than `swap!`? - [x] When you already know the exact new value you want to install - [ ] When you need a local binding - [ ] When you need to coordinate several identities together - [ ] When the state should update asynchronously > **Explanation:** `reset!` sets the value directly. `swap!` derives the next value from the current one. ### What is the better Java analogy for an atom? - [x] An `AtomicReference` that usually holds immutable data - [ ] A mutable local variable in a loop - [ ] A `synchronized` method on every object - [ ] A database transaction log > **Explanation:** The atom manages identity changes, while the stored value usually remains an immutable collection or other immutable value.
Revised on Friday, April 24, 2026