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.
| 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.
swap! Functions Must Be Pureswap! 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))
| 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. |
AtomicReference, AtomicInteger, or synchronized setter in Java?