100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
Programming

Atoms in Clojure

Atoms provide Clojure's simplest managed reference type for safe, synchronous, uncoordinated mutable state backed by lock-free compare-and-swap updates.

State & ConcurrencyIntermediate8 min readJul 10, 2026
Analogies

What Is an Atom?

Clojure encourages you to build programs out of immutable values, but real applications still need places where state legitimately changes over time — a cache, a counter, an in-memory config map. Atoms are the simplest of Clojure's four managed reference types (alongside refs, agents, and vars) for exactly this job: a single, independent value that can be updated synchronously and safely from multiple threads without you writing any locking code yourself.

🏏

Cricket analogy: Like the current run total on the stadium scoreboard being updated by the scorer after each ball — one value, updated independently of other stats like overs or wickets, without needing to coordinate with any other display.

Atoms are one of Clojure's four reference types alongside refs, agents, and vars. Reach for an atom whenever you need a single independent piece of mutable state — a cache, a counter, a config map — with no requirement to coordinate it against any other piece of state.

Creating and Dereferencing Atoms

You create an atom with the atom function, passing an initial value: (atom 0). To read its current value you use deref or the equivalent @ reader macro, both of which return a snapshot of whatever the atom holds at that exact instant. This read never blocks and never locks the atom — it simply hands you the current value, which may already be stale by the time you use it if another thread updates the atom a moment later.

🏏

Cricket analogy: Like glancing at the stadium's giant screen for the current run rate at any instant — what you see is a consistent snapshot at that moment, even though the number keeps changing after you look away, as Virat Kohli's team might check mid-innings.

Updating State with swap!

The idiomatic way to change an atom's value is swap!, which takes the atom and a pure function, applies that function to the atom's current value, and atomically installs the result as the new value using a lock-free compare-and-swap (CAS) operation. If another thread changes the atom between the moment swap! reads the current value and the moment it tries to write the result, the write fails and swap! automatically retries with the freshly read value — meaning your function can execute more than once for a single logical update.

🏏

Cricket analogy: Like a scorer recalculating the strike rate by reapplying the same formula if two ball updates collide — the scorer simply reruns the formula on the newest number rather than corrupting it, similar to how Steve Smith's batting average is recalculated cleanly after every innings.

Because swap! may retry the update function multiple times under contention, never put side effects — println, HTTP calls, database writes, mutating an external object — inside the function passed to swap!. If you need to react to a change, use add-watch instead, which only fires once the atom's value has actually settled.

clojure
(def counter (atom 0))

@counter ;; => 0

(swap! counter inc)
@counter ;; => 1

(swap! counter + 10)
@counter ;; => 11

(reset! counter 0)
@counter ;; => 0

(def config
  (atom {:retries 3, :timeout-ms 5000}
         :validator (fn [m] (pos? (:retries m)))))

(add-watch config :log
  (fn [key ref old-state new-state]
    (println "config changed from" old-state "to" new-state)))

(swap! config assoc :retries 5)

;; (swap! config assoc :retries 0) ;; would throw: validator rejects the update

reset!, Validators, and Watches

Sometimes you want to overwrite an atom's value outright rather than compute it from the current value — that's what reset! is for; it unconditionally installs a new value with no CAS retry logic needed. You can also attach a validator with set-validator!, a predicate that every candidate new value must satisfy or the update is rejected with an exception, and attach one or more watches with add-watch, functions that fire with the key, the reference, the old state, and the new state after a successful change — ideal for logging or triggering further side effects safely.

🏏

Cricket analogy: Like an umpire's outright score correction — reset! is like directly overwriting the scoreboard to a fixed value after an official review (say, setting the total to 250), whereas swap! is like adding runs based on the current total; a validator is like the third umpire refusing an illegal score change.

  • An atom holds one independent value, updated synchronously with no cross-reference coordination.
  • deref/@ gives a non-blocking snapshot read of the current value.
  • swap! computes the new value from the old one via a retryable, lock-free CAS loop — keep the function pure.
  • reset! replaces the value outright, without reference to the old value.
  • Validators enforce invariants by rejecting bad updates before they ever commit.
  • Watches fire after a successful update, useful for side effects and logging.
  • Reach for a ref instead of an atom when two or more values must change together consistently.

Practice what you learned

Was this page helpful?

Topics covered

#Programming#ClojureStudyNotes#AtomsInClojure#Atoms#Clojure#Atom#Creating#StudyNotes#SkillVeris#ExamPrep