Clojure Concurrency Decision Guide — atom vs ref vs agent vs core.async | Episode 30

0views
C
CelesteAI
Description
The Phase 5 finale — and the question that comes up *every time* you start writing stateful Clojure: "which primitive do I reach for?" This episode writes the same hit counter four ways, then lays out a short, blunt decision tree so you can pick in seconds. One counter. Four implementations. Same answer (100) from each. The video's point isn't "one is better" — it's "each one fits a different shape of problem." We close with a decision tree you can keep in your head: when to use atom, when to use ref + dosync, when to use agent, when to use core.async channels, and when `future` / `promise` is the right tool (spoiler — for computation, not state). Student code: https://github.com/GoCelesteAI/clojure-for-beginners/tree/main/episode30 Every keystroke is shown on screen with generous pauses so you can follow along at your own pace. What You'll Learn: - The four shapes of state-change problems — and which primitive fits each - **atom** — one value, caller wants the answer synchronously - **ref + dosync** — multiple values that must change together (invariants) - **agent + send** — fire-and-forget async state (side effects OK in the update fn) - **core.async** — producer/consumer, many workers, backpressure - **future / promise** — one-shot async computation, not state - Common mistakes — `swap!` on two atoms is not atomic; don't reach for a lock; don't block a real thread inside a `go` Timestamps: 0:00 - Intro 0:15 - Preview: pick by the shape of your problem 0:51 - The code — one counter, four implementations 1:28 - Start the REPL 1:48 - atom + swap! 1:58 - ref + alter (in dosync) 2:10 - agent + send (await to sync) 2:25 - core.async channel + go-loop consumer 2:50 - Control-D 3:00 - clj -M:run — all four land at 100 3:10 - Recap + decision tree 4:18 - Phase 5 complete Key Takeaways: 1. The hard part isn't learning atoms, refs, agents, or channels. It's knowing which one fits the problem. 2. **atom**: independent state; caller wants the value now. `swap!` + `inc` is the idiomatic pattern. 3. **ref + dosync**: when you need invariants across multiple pieces of state (the classic bank-transfer). 4. **agent + send**: when the caller shouldn't block. Side effects are fine in the update fn (unlike `swap!`). 5. **core.async**: when you have producer/consumer, many workers, or need backpressure between stages. 6. **future / promise**: for one-shot async *computation*, not mutable state. Don't reach for these when you really want an agent. 7. "When in doubt, start with atom." Promote only when the shape of the problem demands it. Phase 5 complete. Six episodes, five primitives, one mental model. Taught by CelesteAI. Like and subscribe for more Clojure tutorials!
Back to tutorials

Duration

Added to Codegiz

April 20, 2026

📖 Read the articleOpen in YouTube