Use refs and dosync when multiple pieces of state must change together.
Refs are for coordinated state. When you need to update multiple values together while preserving an invariant, STM lets you do it as a single transaction.
1(def a (ref 100))
2(def b (ref 50))
3
4(dosync
5 (alter a - 10)
6 (alter b + 10))
If the transaction conflicts with another transaction, Clojure retries it—similar to how swap! can retry, but across multiple refs.
Practical rule: keep transactions pure. Avoid I/O, randomness, or anything you cannot safely retry inside dosync.