What Are Higher-Order Functions?
Clojure treats functions as first-class values — they can be stored in vars, passed as arguments, and returned as results, just like numbers or strings. A higher-order function (HOF) is any function that takes another function as an argument, returns a function, or both. This capability underlies core Clojure idioms like map, filter, and reduce, and lets you factor out repeated control-flow patterns into reusable combinators.
Cricket analogy: A captain who tells a bowler 'bowl this over however you see fit' is like passing a function as an argument — the over-allocation doesn't care whether the bowler picks an off-cutter or a yorker, it just needs some delivery strategy plugged in.
Passing Functions as Arguments
The most common HOF pattern in Clojure is a function that accepts another function and applies it to each element of a collection, exactly what map, filter, and remove do. You can pass a named function, an anonymous fn, or the #() reader-macro shorthand; all three are ordinary values that satisfy the same contract of being callable with the right arity.
Cricket analogy: Selecting Jasprit Bumrah specifically for the death overs because his yorker function handles that situation best is like choosing which function to hand to apply-twice — the caller decides the exact behavior at the call site.
Returning Functions and Closures
A HOF can also manufacture and return a new function, capturing values from its enclosing scope in a closure. (defn make-adder [n] (fn [x] (+ x n))) returns a fresh function each call, and that returned function 'remembers' n even after make-adder has finished executing — this is how Clojure implements configurable behavior without mutable state.
Cricket analogy: A bowling coach who customizes a bowling machine to always deliver at 140kph for a specific batsman's net session, and that setting persists every time the machine fires, mirrors a closure remembering n=5 across every call to add5.
(defn make-adder [n]
(fn [x] (+ x n)))
(def add5 (make-adder 5))
(add5 10) ;=> 15Composing and Adapting Functions: comp, partial, complement
Clojure's core library ships HOF combinators that build new functions out of old ones without writing fn by hand. (comp f g) returns a function that applies g first, then f, to the result; (partial f a) fixes the first argument(s) of f and returns a function expecting the rest; (complement pred) returns a function that negates pred's boolean result. Composing these is idiomatic Clojure and avoids nested lambda boilerplate.
Cricket analogy: Chaining a batsman's 'leave the short ball' decision function with a 'then check for a no-ball' umpire function is like (comp f g) — the second check only runs after the first resolves, right to left in call order.
(def clean-and-shout (comp clojure.string/upper-case clojure.string/trim))
(clean-and-shout " hello ") ;=> "HELLO"
(def add10 (partial + 10))
(add10 5) ;=> 15
(def odd?* (complement even?))
(odd?* 3) ;=> truecomp composes right-to-left, matching mathematical function composition notation (f ∘ g)(x) = f(g(x)) — the rightmost function runs first.
- Functions in Clojure are first-class values — they can be assigned to vars, passed as arguments, and returned from other functions.
- A higher-order function either takes a function as an argument, returns a function, or both.
- map, filter, and reduce are the most commonly used built-in HOFs for sequence processing.
- Closures let a returned function retain access to values from its enclosing scope, like make-adder retaining n.
- comp composes functions right-to-left; partial fixes leading arguments; complement negates a predicate's result.
- Anonymous functions can be written with fn or the #() reader macro for concise inline logic.
- HOFs let you eliminate repetitive loop boilerplate by extracting the varying behavior into a passed-in function.
Practice what you learned
1. What makes a function 'higher-order' in Clojure?
2. What does (make-adder 5) return in (defn make-adder [n] (fn [x] (+ x n)))?
3. In (comp f g), which function is applied first to the input?
4. What does (partial + 10) produce?
5. Which is equivalent to (fn [x] (* x x)) using the #() reader macro?
Was this page helpful?
You May Also Like
map, filter, reduce in Clojure
Master the three foundational sequence-processing functions that let you transform, select, and accumulate data declaratively instead of writing explicit loops.
Threading Macros
Learn how ->, ->>, some->, some->>, and as-> rewrite deeply nested Clojure expressions into readable, linear pipelines without changing runtime behavior.
Lazy Sequences
Understand how Clojure's lazy sequences defer computation until it's needed, enabling infinite sequences, and learn the chunking and memory pitfalls that trip up beginners.
Related Reading
Related Study Notes in Programming
Browse all study notesApache Spark Study Notes
Programming · 30 topics
ProgrammingApache Flink Study Notes
Programming · 30 topics
ProgrammingHadoop Study Notes
Programming · 30 topics
ProgrammingSnowflake Study Notes
Programming · 30 topics
ProgrammingApache Airflow Study Notes
Programming · 30 topics
Programmingdbt (Data Build Tool) Study Notes
Programming · 30 topics