Browse Learn Clojure Foundations as a Java Developer

Clojure Concurrency Primitives

Choose Clojure atoms, refs, and agents by separating immutable values from changing identities and deciding how state updates should be coordinated.

Java concurrency often starts with threads, executors, locks, atomics, and concurrent collections. Clojure starts from a different default: immutable values are safe to share, and changing identities are managed explicitly.

The first question is not “which lock do I need?” The first question is:

What identity changes over time, and how should updates to that identity be coordinated?

The Core Choices

Primitive Best fit Timing Coordination
Atom One independent identity Synchronous Compare-and-set retry
Ref Multiple coordinated identities Synchronous transaction Software transactional memory
Agent One independent identity Asynchronous Queued actions
    flowchart TD
	    A["Changing state?"] --> B{"Multiple related identities?"}
	    B -->|Yes| C["ref + dosync"]
	    B -->|No| D{"Caller needs result now?"}
	    D -->|Yes| E["atom"]
	    D -->|No| F["agent"]

This is a first-pass selection rule, not a complete concurrency architecture.

Atoms: Independent Synchronous State

Use an atom when one identity changes independently and the caller should see the updated value immediately.

1(def stats (atom {:requests 0 :errors 0}))
2
3(defn record-request [state]
4  (update state :requests inc))
5
6(swap! stats record-request)
7@stats
8;; => {:requests 1, :errors 0}

swap! may retry the update function under contention. Keep the update function pure. Do not put logging, sending, database writes, or “must happen once” effects inside it.

Refs: Coordinated In-Memory Transactions

Use refs when multiple identities must change together:

1(def checking (ref 1000))
2(def savings (ref 2000))
3
4(defn transfer! [amount]
5  (dosync
6    (alter checking - amount)
7    (alter savings + amount)))

Refs are for coordinated in-memory state. They are not a replacement for database transactions.

Agents: Asynchronous State Transitions

Use an agent when updates can be queued and the caller does not need the updated value immediately.

1(def audit-log (agent []))
2
3(send audit-log conj {:event :login :user-id 42})

Agents are useful for serialized asynchronous state changes around one identity. They are not a general distributed queue or actor system.

What These Primitives Are Not

Need More likely tool
Run a task later future, executor, scheduler, or queue
Coordinate streaming messages core.async, queue, broker, or reactive library
Persist business transactions Database transaction
Share immutable data No reference type needed

Clojure’s reference types manage state over time. Do not use them just because a task is asynchronous.

Review Checklist

Ask these questions:

  1. Can this value just be passed as immutable data?
  2. Is there exactly one changing identity?
  3. Must several identities change together?
  4. Does the caller need the result immediately?
  5. Is the update function retry-safe and free of side effects?

That checklist catches most early concurrency design mistakes.

Quiz: Concurrency Primitives

### Which primitive fits one independent identity with synchronous updates? - [x] Atom. - [ ] Ref. - [ ] Agent. - [ ] Namespace. > **Explanation:** Atoms are the normal fit for independent state updated in the caller's flow. ### Which primitive coordinates multiple in-memory identities transactionally? - [x] Refs inside `dosync`. - [ ] Atoms without `swap!`. - [ ] Agents only. - [ ] Keywords. > **Explanation:** Refs and software transactional memory coordinate related in-memory updates. ### Why should `swap!` update functions be pure? - [x] They may be retried under contention. - [ ] Atoms can only store numbers. - [ ] Clojure forbids all side effects. - [ ] `swap!` only runs at compile time. > **Explanation:** A retry can call the update function more than once. Side effects inside the update function can therefore happen more than once. ### True or False: Immutable values need a reference type just to be safely shared. - [ ] True - [x] False > **Explanation:** Immutable values can be shared safely. Reference types are for identities that change over time.
Revised on Saturday, May 23, 2026