Browse Clojure Foundations for Java Developers

Passing Functions as Arguments in Clojure

Learn how Clojure APIs like map, filter, and reduce take behavior as an argument, and how to choose between named functions, anonymous functions, and keyword functions.

Passing a function as an argument is the move that makes Clojure’s collection programming feel different from imperative Java loops.

Instead of writing the control flow yourself, you give an existing function the behavior it needs.

That is what happens in all of these:

1(map inc [1 2 3])
2(filter even? [1 2 3 4])
3(reduce + [1 2 3 4])

The library already knows how to walk the data. You supply the transformation, predicate, or reduction step.

The Core Pattern

These are the most common higher-order collection functions you will reach for first:

Function The function argument means Typical job
map “How should each item change?” Transform each value
filter “Which items should stay?” Keep matching items
remove “Which items should go away?” Drop matching items
reduce “How do I combine the next item into the result?” Aggregate to one value
sort-by “Which derived key should determine order?” Sort by a projection

This is why higher-order functions feel natural in Clojure: the function argument names the one piece of behavior the library cannot guess for you.

A Concrete Example

Suppose you need the total amount of paid orders.

 1(defn paid? [{:order/keys [status]}]
 2  (= status :paid))
 3
 4(defn amount [{:order/keys [amount]}]
 5  amount)
 6
 7(defn total-paid-amount [orders]
 8  (->> orders
 9       (filter paid?)
10       (map amount)
11       (reduce + 0M)))

There are three function arguments in that pipeline:

  • paid? tells filter which orders to keep
  • amount tells map what to extract
  • + tells reduce how to combine numbers

This is what “pass behavior, not control flow” looks like in ordinary code.

Choosing What Kind Of Function To Pass

Not every function argument should look the same.

Form Example Good fit
Named function paid? Reused logic or logic worth naming
Anonymous function literal #(>= (:order/amount %) 1000M) Short one-off behavior
Keyword as function :order/id Extract one key from each map
Existing core function inc, +, count Clear built-in behavior

Here is the same projection using a keyword function:

1(map :order/id orders)

That is idiomatic because keywords can act as lookup functions on maps.

A Java Comparison That Is Actually Useful

Java developers often compare this to streams, and that comparison is good as long as you do not push it too far.

Java streams Clojure
.map(x -> x.getAmount()) (map :order/amount orders) or (map amount orders)
.filter(Order::isPaid) (filter paid? orders)
.reduce(BigDecimal.ZERO, BigDecimal::add) (reduce + 0M amounts)

The difference is not only syntax. Clojure treats these function arguments as the default shape of data processing, not as a special stream API.

Arity Still Matters

Passing functions around does not erase function contracts.

For example:

  • map with one collection calls its function with one argument
  • map with two collections calls its function with two arguments
  • reduce expects a reducing function that can combine an accumulated result with the next item

That means this works:

1(map vector [:a :b :c] [1 2 3])
2;; => ([:a 1] [:b 2] [:c 3])

because vector accepts two arguments.

But if you pass a one-argument function where map will call it with two arguments, that is an arity error, not a style issue.

The Most Common Early Mistakes

Mistake Why it happens Better version
Passing a result instead of a function Java habits make the call site look “one step too far” (map normalize orders), not (map (normalize order) orders)
Writing a large #(...) literal The short syntax looks convenient at first Use fn or a named function when the logic has real weight
Reaching for mutation inside the callback Imperative habits survive the pipeline rewrite Keep the callback focused on returning a value
Forgetting laziness map and filter often return lazy seqs Realize with into [], doall, or a consumer when needed

When Naming The Function Helps

If the function argument names a real business rule, naming it usually improves the code.

1(defn discount-eligible? [order]
2  (and (= :paid (:order/status order))
3       (>= (:order/amount order) 500M)))
4
5(filter discount-eligible? orders)

That is easier to review than embedding the entire rule inline every time it appears.

If the behavior is truly tiny and local, anonymous syntax is still fine:

1(filter #(>= (:order/amount %) 500M) orders)

The tradeoff is readability, not ideology.

Knowledge Check

### In `(filter paid? orders)`, what role does `paid?` play? - [x] It is the predicate function that decides which orders stay - [ ] It is the collection being filtered - [ ] It is a mutable accumulator - [ ] It forces `filter` to return a vector > **Explanation:** `filter` walks the collection itself. `paid?` supplies only the rule for keeping or discarding each item. ### Which form is often the clearest way to extract a single key from each map in a collection? - [x] A keyword function such as `:order/id` - [ ] A Java-style getter string such as `"getId"` - [ ] A mutable callback object - [ ] A macro around `map` > **Explanation:** In Clojure, keywords can act as functions on maps, so `(map :order/id orders)` is a common and readable pattern. ### Why can `(map vector [:a :b] [1 2])` work? - [x] `map` calls `vector` with one item from each collection, and `vector` can accept those arguments - [ ] `vector` is automatically converted into a reducer - [ ] `map` ignores arity when multiple collections are present - [ ] `vector` mutates both collections in place > **Explanation:** With multiple input collections, `map` passes multiple arguments to the function. The function must support that arity. ### What is the main problem with passing `(normalize order)` into `map` instead of `normalize`? - [x] It passes the result of calling `normalize`, not a function to apply later - [ ] It makes `map` eager instead of lazy - [ ] It forces the collection to become a list - [ ] It prevents `normalize` from returning a value > **Explanation:** `map` needs a function value. `(normalize order)` invokes the function immediately and hands `map` the result, which is not the same thing.
Revised on Friday, April 24, 2026