Browse Learn Clojure Foundations as a Java Developer

Use Atoms for Independent State

Use Clojure atoms when one in-process identity must change synchronously, and keep `swap!` update functions pure because they may be retried under contention.

An atom is Clojure’s everyday reference type for one independent piece of state. It is closest to a Java AtomicReference holding an immutable value, but the normal update style is functional: provide a function from old value to new value.

Use an atom when the state does not need to coordinate with another reference. Counters, local caches, in-memory configuration snapshots, and REPL-managed application state often fit.

Atom Basics

Operation Meaning
(atom initial) Create a reference with an initial value.
@state or (deref state) Read the current value.
(swap! state f args...) Atomically replace the value with (f old args...).
(reset! state value) Replace the value directly.
compare-and-set! Low-level conditional replacement.
 1(def metrics
 2  (atom {:requests 0
 3         :errors 0}))
 4
 5(defn record-request! [ok?]
 6  (swap! metrics
 7         (fn [m]
 8           (-> m
 9               (update :requests inc)
10               (update :errors
11                       (fnil + 0)
12                       (if ok? 0 1))))))

The atom is mutable identity. The map stored inside it is still an immutable value. Each update produces a new map.

Why swap! Functions Must Be Pure

swap! reads the current value, applies your function, and tries to install the result. If another thread changed the atom first, Clojure may call your function again with a newer value.

That retry behavior is why update functions should not send emails, write files, publish messages, or call payment APIs.

1;; Avoid: the side effect could happen more than once.
2(swap! metrics
3       (fn [m]
4         (println "recording request")
5         (update m :requests inc)))

Keep the transition pure, then perform effects around it when needed.

1(defn increment-request [m]
2  (update m :requests inc))
3
4(defn record-request-and-log! []
5  (let [after (swap! metrics increment-request)]
6    (println "request count:" (:requests after))
7    after))

Good and Bad Fits

Fit Why
One service-local cache One identity can be replaced atomically.
Feature flag snapshot loaded from config Readers need the latest immutable value.
REPL-managed app system map Development workflow often benefits from explicit reset.
Coordinated bank transfer Bad fit: two balances must change together unless represented as one value.
Background job queue Bad fit: queuing and backpressure usually need a queue, channel, agent, or executor.

Java Review Questions

  • Would this have been an AtomicReference, AtomicInteger, or synchronized setter in Java?
  • Is there exactly one changing identity?
  • Can the whole transition be represented as a pure function?
  • Would another state location become inconsistent if this update succeeds alone?
  • Could contention make the update function retry?

Knowledge Check

### What kind of state is an atom designed for? - [x] One independent identity that changes synchronously. - [ ] Several identities that must change in one transaction. - [ ] A namespace binding that should be dynamically scoped. - [ ] A durable value shared across multiple JVM processes. > **Explanation:** Atoms are in-process references for independent synchronous state. They do not provide multi-reference transactions or persistence. ### Why should a `swap!` function avoid side effects? - [x] It may be called more than once if the atom changes during the update attempt. - [ ] It runs in a database transaction. - [ ] It always runs on a separate agent thread. - [ ] It cannot return maps. > **Explanation:** `swap!` uses a retry loop. Retried side effects can duplicate observable work. ### What should you check before choosing an atom? - [x] Whether another value must be updated consistently with this one. - [ ] Whether the value contains keywords. - [ ] Whether the namespace has a docstring. - [ ] Whether the update uses a vector. > **Explanation:** If another identity must change consistently with this one, an atom may be too narrow unless both facts are stored in one immutable aggregate.
Revised on Saturday, May 23, 2026