Browse Learn Clojure Foundations as a Java Developer

Choose Practical Ref Use Cases

Use refs for practical in-process coordination problems such as inventory reservation, paired indexes, and workflow snapshots, while leaving durability and distributed truth to external systems.

Refs are a focused tool. They are excellent when a small set of in-memory identities must move together, and they are a poor substitute for a database, durable queue, or distributed lock.

Use them when the coordination problem is local to one JVM and the invariant is important enough to justify a transaction.

Good and Bad Fits

Scenario Fit Reason
Inventory count plus reservation log Good Stock and reservation record must agree.
Primary map plus secondary index Good Both representations must update together.
Simulation state with several entities Sometimes Useful when entities are separate refs and coordinated moves matter.
Single metrics counter Poor Atom is simpler.
Order ledger for a real business Poor Needs durable storage.
Work queue with backpressure Poor Queue, channel, or broker fits better.

The line is practical: if losing the state on JVM restart is unacceptable, refs should not be the source of truth.

Example: Primary Map and Index

This example keeps a user map and an email index consistent. Either both refs update, or neither does.

 1(def users-by-id (ref {}))
 2(def id-by-email (ref {}))
 3
 4(defn add-user! [user]
 5  (dosync
 6    (let [{:keys [id email]} user]
 7      (when (contains? @id-by-email email)
 8        (throw
 9          (ex-info "email already used"
10                   {:email email})))
11      (alter users-by-id assoc id user)
12      (alter id-by-email assoc email id))))

This is a good ref example because the two refs represent different identities with one invariant: email lookup must point at the same user stored by id.

If This Were Java

Java design pressure Clojure STM response
Need lock ordering across two maps Put both map updates in one dosync.
Need to avoid deadlock Let STM coordinate conflicts instead of hand-ordering locks.
Need to test the invariant Test the pure validation and update logic around immutable data.
Need durable user records Use a database; refs can still help for local derived state.

STM can simplify in-memory coordination, but it should not blur the boundary between memory and persistence.

Production Guidance

  • Keep refs private to a component or namespace when possible.
  • Store ordinary immutable data in refs.
  • Use refs for derived or process-local coordination unless you have a clear reason otherwise.
  • Keep transaction bodies small enough to review.
  • Prefer a database transaction when business correctness depends on durability.

Knowledge Check

### Which use case is a strong fit for refs? - [x] Updating a primary map and secondary index together in one JVM. - [ ] Incrementing one independent counter. - [ ] Persisting orders across service restarts. - [ ] Running blocking I/O asynchronously. > **Explanation:** The primary map and index are separate identities with one consistency rule. The other cases fit atoms, databases, or async tools better. ### Why are refs a poor source of durable business truth? - [x] They coordinate in-memory state, not persistent storage across restarts. - [ ] They cannot hold maps. - [ ] They only work in browser code. - [ ] They make every update asynchronous. > **Explanation:** Refs live in the JVM process. Durable truth belongs in a database, event log, file, or another external store. ### What is a good production rule for refs? - [x] Keep them private to a component or namespace when possible. - [ ] Put every field in its own public ref. - [ ] Use refs for every kind of state. - [ ] Perform external I/O inside `dosync`. > **Explanation:** Narrow ownership makes invariants easier to enforce and keeps retryable code under control.
Revised on Saturday, May 23, 2026