100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
Programming

Threading Macros

Learn how ->, ->>, some->, some->>, and as-> rewrite deeply nested Clojure expressions into readable, linear pipelines without changing runtime behavior.

Functional ProgrammingIntermediate8 min readJul 10, 2026
Analogies

Why Threading Macros Exist

Deeply nested function calls like (reduce + (filter even? (map inc coll))) read inside-out, which is hard to follow because execution order runs opposite to reading order. Threading macros rewrite that nesting into a linear top-to-bottom or left-to-right pipeline that reads in the actual order operations happen, without changing what code executes — they're a purely syntactic (macro-level) transformation performed at compile time.

🏏

Cricket analogy: Reading a scorecard line like 'caught Kohli, bowled by Bumrah, off a yorker, in the 49th over' backwards would be confusing — threading macros reorder the sentence of function calls into the natural, chronological reading order, like a properly written commentary line.

The Thread-First Macro: ->

-> (thread-first) takes a value and threads it through a series of forms, inserting it as the first argument of each subsequent form; it's the natural choice for functions that take 'the thing being operated on' as their first parameter, like most map, assoc, and update calls on a single data structure. (-> m (assoc :a 1) (update :b inc)) expands to (update (assoc m :a 1) :b inc).

🏏

Cricket analogy: A batsman's innings threaded through 'start at crease -> face first ball -> get to fifty -> get to century,' where each stage takes the current innings-state as its first input, mirrors thread-first's data-as-first-arg pattern.

clojure
(-> {:a 1 :b 2}
    (assoc :c 3)
    (update :a inc)
    (dissoc :b))
;=> {:a 2 :c 3}

Thread-Last (->>) and the Short-Circuiting some-> / some->>

->> (thread-last) inserts the threaded value as the last argument instead of the first, which fits sequence functions like map, filter, and reduce where the collection is conventionally the final parameter; mixing -> and ->> incorrectly is a very common Clojure bug, since assoc expects the map first but filter expects the collection last. some-> and some->> thread the value through the same way but short-circuit to nil the moment any step returns nil, avoiding a chain of errors when data might be missing.

🏏

Cricket analogy: A DRS review chain that stops immediately at 'ball tracking shows missing leg stump' without checking the remaining checks is exactly some->'s short-circuit-on-nil behavior.

clojure
(->> [1 2 3 4 5]
     (filter odd?)
     (map inc)
     (reduce +))
;=> 12  ; odd? -> (1 3 5), inc -> (2 4 6), sum -> 12

(some-> {:user {:profile nil}}
        :user
        :profile
        :email) ;=> nil, chain stops safely at :profile

as-> lets you bind the threaded value to a chosen symbol so it can be placed in any argument position, not just first or last: (as-> 5 x (+ x 1) (str "total: " x)) threads x wherever it appears in each form.

  • -> (thread-first) inserts the threaded value as the first argument of each subsequent form.
  • ->> (thread-last) inserts the threaded value as the last argument of each subsequent form.
  • Threading macros are purely syntactic rewrites performed at macro-expansion time, not runtime behavior changes.
  • Mixing up -> and ->> for functions with the wrong argument position is a very common Clojure bug.
  • some-> and some->> short-circuit to nil the moment any step in the chain returns nil.
  • as-> allows binding the threaded value to a named symbol so it can be placed in any argument position.
  • Threading macros greatly improve readability of multi-step transformations compared to deeply nested calls.

Practice what you learned

Was this page helpful?

Topics covered

#Programming#ClojureStudyNotes#ThreadingMacros#Threading#Macros#Exist#Thread#Concurrency#StudyNotes#SkillVeris