Learn when to use `->`, `->>`, `some->`, `cond->`, and `as->` so Clojure pipelines stay readable and match function argument shape.
Threading macro: A macro that takes a starting value and inserts it into a sequence of forms so a transformation pipeline reads top to bottom instead of inside out.
Threading macros are idiomatic Clojure because they make transformation flow visible. They do not make code “more functional” by themselves. They simply make it easier to see how a value moves through a series of steps. That matters a lot in a language where data is often transformed through many small functions.
Without threading, multi-step transformations quickly turn into nested calls that read from the inside out.
1(update (assoc person :hair-color :gray) :age inc)
That is valid, but the reader has to mentally unwind it. The thread-first macro -> makes the same transformation read in application order:
1(-> person
2 (assoc :hair-color :gray)
3 (update :age inc))
The main gain is not fewer characters. It is that the data flow is now obvious.
-> for Data-Structure Style APIsUse -> when the value being transformed belongs in the first argument position. That is common for functions such as:
assocupdatedissocget1(-> {:name "Ava" :age 39}
2 (assoc :role :admin)
3 (update :age inc)
4 (dissoc :temp-token))
This style works well for maps and other data-structure transformations because most of the relevant core functions take the structure first.
->> for Sequence PipelinesUse ->> when the value belongs in the last argument position. That is the normal shape for many sequence functions:
mapfilterremovetakereduce1(->> (range 10)
2 (filter odd?)
3 (map #(* % %))
4 (reduce +))
The rule of thumb is simple: if the collection naturally sits at the end of each call, ->> usually reads best.
The most common mistake is choosing a threading macro because it looks familiar rather than because it matches the argument position of the functions involved.
1(->> {:name "Ava" :age 39}
2 (assoc :role :admin))
That is wrong because assoc expects the map first, not last. The fix is not mystical. It is simply to use the macro that matches the function signature.
some-> and some->> for Nil-Sensitive Pipelinessome-> and some->> are useful when a pipeline should stop if one stage produces nil.
1(some-> request
2 :headers
3 (get "x-request-id")
4 parse-request-id)
If any stage returns nil, the rest of the pipeline is skipped and the whole expression becomes nil.
This is especially useful around:
nilDo not use some-> just because it exists. Use it when nil is a legitimate short-circuit condition.
cond-> and cond->> for Conditional Transformscond-> is not a conditional branch like cond. It conditionally applies transformations to the same value.
1(cond-> {:name "Ava"}
2 include-role? (assoc :role :admin)
3 include-id? (assoc :id "u-42"))
That makes it ideal for building maps, queries, and option structures where each transformation is optional.
Important nuance: cond-> does not short-circuit after the first matching condition. It evaluates each test and applies each matching transformation in order.
as-> When Neither First Nor Last WorksSometimes the value belongs in different argument positions at different steps. That is what as-> is for.
1(as-> [:foo :bar] value
2 (map name value)
3 (first value)
4 (.substring value 1))
as-> is more flexible, but also less instantly readable than -> or ->>. Use it when the pipeline genuinely has mixed insertion points, not as a default.
flowchart TD
A["Start with a value"] --> B{"Where does the value go?"}
B --> C["First argument -> use `->`"]
B --> D["Last argument -> use `->>`"]
B --> E["Only continue while non-nil -> use `some->` or `some->>`"]
B --> F["Apply optional transforms -> use `cond->` or `cond->>`"]
B --> G["Mixed positions -> use `as->`"]
Threading is not automatically clearer than everything else.
Avoid it when:
Sometimes a small let with named intermediate values is the more readable move.
->> for APIs that expect the value first-> for sequence transformations that want the collection lastcond-> behaves like condas-> when ordinary refactoring would be clearer-> is usually for data-structure transformations.->> is usually for sequence pipelines.some-> helps when nil should stop the pipeline.cond-> conditionally applies multiple transforms to one value.as-> is the escape hatch for mixed insertion points.