Learn what lock-free and wait-free really mean, where Clojure uses CAS-based lock-free techniques, and why most application code should prefer higher-level concurrency models.
Lock-free: A progress guarantee where some thread completes in a finite number of steps.
Wait-free: A stronger guarantee where every thread completes in a finite number of steps.
These terms are precise. They are not marketing labels for “fast concurrent code.” That precision matters because many articles overstate what ordinary application code can promise.
In Clojure, atoms rely on compare-and-swap and therefore use lock-free techniques for single-reference updates. But that does not mean all atom-based code is wait-free, and it definitely does not mean refs or agents are lock-free programming models in the same sense.
The most direct lock-free building block in everyday Clojure is the atom:
1(def queue-depth (atom 0))
2
3(swap! queue-depth inc)
Under the hood, swap! retries with CAS when contention occurs. That gives you atomic single-reference updates without explicit locks, but not a per-thread completion guarantee. A hot atom can retry many times under contention.
Refs use STM. Agents use queued asynchronous execution. Both are valuable, but they solve different coordination problems and should not be described as wait-free application tools.
Wait-free algorithms are hard to design, hard to verify, and usually reserved for specialized low-level structures. Most production systems do not need to hand-roll them. They need predictable correctness, good enough throughput, and clear ownership boundaries.
That is why most Clojure applications should choose among:
These tools solve the problem you probably have. Wait-free custom algorithms solve a much narrower class of problems.
Custom lock-free design is most justified when:
Even then, the right implementation may come from proven JVM libraries rather than handwritten low-level code.
Treat lock-free and wait-free as precise progress guarantees, not style words. In Clojure application code, the practical question is usually not “Can I build a wait-free queue?” but “Can I remove shared mutable coordination entirely?” Very often the answer is yes, and that answer is far cheaper to maintain.