See where returning functions pays off in real Clojure code: validators, adapters, middleware-like composition, and configuration-driven behavior.
The easiest way to misuse this pattern is to treat it as a clever trick.
The easiest way to use it well is to notice a recurring shape:
That shape comes up all the time in application code.
Suppose different customers have different order approval thresholds.
1(defn approval-checker [minimum-total]
2 (fn [order]
3 (>= (:order/total order) minimum-total)))
4
5(def standard-approval? (approval-checker 100M))
6(def enterprise-approval? (approval-checker 1000M))
That gives you specialized behavior without branches scattered across the codebase.
This is often clearer than:
Java integration often needs “convert this shape into that shape” behavior.
Returning a function is a clean way to preconfigure an adapter.
1(defn map->headers-writer [header-order]
2 (fn [response]
3 (mapv (fn [header-name]
4 [header-name (get-in response [:headers header-name])])
5 header-order)))
Now one part of the system can choose the header layout once, and the returned function can be reused wherever that response shape appears.
This is a very common Clojure-on-the-JVM pattern:
Sometimes the thing you want to capture is not a number or option. It is a dependency such as a clock, logger, or HTTP client.
1(defn order-recorder [log-fn]
2 (fn [order]
3 (log-fn {:event :order/received
4 :order/id (:order/id order)})
5 order))
This is useful when you want to:
That is a much more functional shape than passing a large object graph around just so one downstream call can log something.
One of the most powerful uses of returned functions is wrapping one function with another.
1(defn wrap-timing [handler]
2 (fn [request]
3 (let [start (System/nanoTime)
4 response (handler request)
5 elapsed (- (System/nanoTime) start)]
6 (assoc response :timing/nanos elapsed))))
This returns a new handler built from an old one.
That shape is everywhere in Clojure web stacks and service composition:
Java developers often recognize this as decorator-like behavior. The Clojure version is smaller because the decorated unit is just a function.
Sometimes you want to configure how data should be extracted or presented.
1(defn field-selector [fields]
2 (fn [entity]
3 (select-keys entity fields)))
4
5(def customer-summary
6 (field-selector [:customer/id :customer/email :customer/tier]))
That is especially useful when the same projection is reused across:
| Problem shape | Returning a function helps because… |
|---|---|
| Validation depends on a threshold or policy | You specialize the rule once |
| Interop depends on external shape or schema | You specialize the adapter once |
| Middleware needs to wrap behavior | You return a new composed function |
| Formatting depends on selected fields or options | You reuse one configured formatter many times |
This pattern is weaker when:
For example, if you are capturing half a dozen unrelated values and the reader has to mentally reconstruct hidden state, a small named map or record may be clearer.
If you can describe the outer function as:
“build me a specialized version of this behavior”
then returning a function is often the right move.
If you can only describe it as:
“it sort of packages some stuff up”
then the design may still be too vague.