Choose swap! when an atom update depends on the current value, use reset! only for direct replacement, and keep swap! functions side-effect free because Clojure may retry them.
Atom updates should make the transition visible in one place. If the new value depends on the old value, use swap!. If you already have the whole replacement value, use reset!.
The important production rule is that the function passed to swap! may run more than once under contention. Treat it like a pure calculation, not a place to perform irreversible work.
swap! reads the atom, applies your function, and tries to install the result. If another thread wins the race first, Clojure retries with the newer value.
1(def metrics
2 (atom {:ok 0
3 :fail 0}))
4
5(defn record [m ok?]
6 (update m
7 (if ok? :ok :fail)
8 inc))
9
10(defn record! [ok?]
11 (swap! metrics record ok?))
The transition function is easy to test without the atom:
1(record {:ok 1 :fail 0} false)
2;; => {:ok 1, :fail 1}
That is the design you want: business update logic is pure, and the atom only supplies atomic replacement.
reset! is useful when the replacement value is already known. Common examples include reloading a configuration snapshot, clearing development state, or setting a known test fixture.
1(reset! metrics {:ok 0
2 :fail 0})
Do not use reset! for read-modify-write logic. This pattern loses updates:
1(reset! metrics
2 (update @metrics
3 :ok
4 inc))
Use swap! instead:
1(swap! metrics update :ok inc)
| Need | Prefer | Reason |
|---|---|---|
| Increment, append, assoc, dissoc, or merge with current state | swap! |
The new value depends on the old value. |
| Replace state with a complete new snapshot | reset! |
No current value is needed. |
| Conditionally replace only if still unchanged | compare-and-set! |
Useful for rare low-level interop or lock-free algorithms. |
| Coordinate several identities | Refs or one aggregate atom value | One atom update cannot coordinate separate references. |
Most application code should use swap! and reset!. Treat compare-and-set! as a specialized tool, not the normal atom API.
This is unsafe:
1(swap! metrics
2 (fn [m]
3 (println "recording")
4 (update m :ok inc)))
If the atom changes concurrently, the function may be retried and the print may happen more than once. Replace the value first, then perform observable work based on the result.
1(let [after
2 (swap! metrics
3 update
4 :ok
5 inc)]
6 (println "ok count:" (:ok after)))
The same rule applies to database writes, Kafka publishes, payment calls, metrics emission, and Java object mutation.