Browse Learn Clojure Foundations as a Java Developer

Update Atoms with swap! and reset!

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 for State Transitions

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 for Direct 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)

Update Choice Table

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.

Keep Effects Outside Atom Updates

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.

Knowledge Check

### When should `swap!` be preferred over `reset!`? - [x] When the new value depends on the atom's current value. - [ ] When the atom should be deleted. - [ ] When the update must coordinate several refs. - [ ] When the code should bypass atomicity. > **Explanation:** `swap!` applies a transition function to the current value and installs the result atomically. That is the right shape for read-modify-write updates. ### Why must a `swap!` function avoid irreversible side effects? - [x] Clojure may retry the function if another thread updates the atom first. - [ ] `swap!` always runs in a database transaction. - [ ] `swap!` only accepts functions with no arguments. - [ ] Atoms cannot hold maps. > **Explanation:** `swap!` uses retry logic. A pure function can be retried safely; an email, payment, or Java mutation cannot. ### What is wrong with `(reset! metrics (update @metrics :ok inc))`? - [x] The read and replacement are separate, so a concurrent update can be lost. - [ ] `update` cannot work with maps. - [ ] `reset!` only works with strings. - [ ] Dereferencing an atom always throws. > **Explanation:** This is a non-atomic read-modify-write sequence. Use `swap!` when the replacement depends on the previous value.
Revised on Saturday, May 23, 2026