Browse Clojure Foundations for Java Developers

Practical Use Cases for Returning Functions

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:

  • choose a rule, threshold, or dependency once
  • get back a function
  • apply that function many times later

That shape comes up all the time in application code.

Use Case 1: Build Validators From Configuration

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:

  • threading threshold values through every call
  • storing tiny strategy classes
  • repeating the same comparison logic in multiple places

Use Case 2: Create Interop Adapters Once

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:

  • outer function chooses environment, dependency, or schema shape
  • returned function performs the repeated adaptation

Use Case 3: Capture One Dependency At The Boundary

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:

  • inject the dependency once
  • keep the returned function easy to compose into pipelines
  • isolate effectful edges from the rest of the program

That is a much more functional shape than passing a large object graph around just so one downstream call can log something.

Use Case 4: Middleware-Like Wrapping

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:

  • add logging
  • add timing
  • add auth checks
  • add response decoration

Java developers often recognize this as decorator-like behavior. The Clojure version is smaller because the decorated unit is just a function.

Use Case 5: Pre-Build A Projection Or Formatter

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:

  • API responses
  • CSV exports
  • logging
  • debugging or support tooling

A Quick Decision Table

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

When Not To Use It

This pattern is weaker when:

  • the function will only be used once
  • the captured values are so numerous that the closure becomes hard to understand
  • returning plain data would keep the design simpler

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.

A Good Litmus Test

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.

Knowledge Check

### What is the main advantage of `approval-checker` returning a function? - [x] It creates specialized validation behavior once and reuses it for many orders - [ ] It converts all orders into mutable objects - [ ] It forces threshold values into global state - [ ] It replaces all need for plain data configuration > **Explanation:** The outer function captures the threshold once, and the returned validator can then be reused wherever that approval policy is needed. ### In `wrap-timing`, what does the returned function represent? - [x] A new handler that adds timing behavior around an existing handler - [ ] A map of timing settings with no executable behavior - [ ] A mutable timer shared by all requests - [ ] A sorting function for response maps > **Explanation:** `wrap-timing` takes one function and returns another function that wraps it, which is the essence of middleware-style composition. ### Which use case is the strongest fit for returning a function? - [x] You want to configure an adapter or validator once and apply it repeatedly later - [ ] You need to return a static list of field names - [ ] You need one immediate calculation with no later reuse - [ ] You want to avoid ever passing plain data structures > **Explanation:** The pattern is strongest when configuration happens now and repeated execution happens later. ### When might returning a function be a worse design than returning data? - [x] When the closure would capture many unrelated values and make the behavior hard to understand - [ ] When the function is pure - [ ] When the code uses immutable data - [ ] When the function can be called more than once > **Explanation:** A closure should make behavior clearer, not hide too much state. If the captured context becomes opaque, a small explicit data shape may be easier to reason about.
Revised on Friday, April 24, 2026