Browse Clojure Foundations for Java Developers

First-Class and Higher-Order Functions

Treat functions as values so you can build reusable, composable transformations over data.

In Clojure, functions are ordinary values. You can pass them around, store them, return them, and combine them. That sounds familiar if you already use Java lambdas, but the role is much bigger in Clojure. Here, functions are not just a convenience feature. They are one of the main building blocks of everyday design.

First-class function: a function that can be passed, returned, stored, and treated like any other value.

Higher-order function: a function that accepts another function, returns one, or both.

For a Java engineer, the real shift is this: in Clojure, reusable behavior is often expressed as a function argument instead of an interface implementation.

From Strategy Objects To Function Values

Consider a small reporting helper:

1(defn count-matching [pred xs]
2  (count (filter pred xs)))
3
4(count-matching even? [1 2 3 4])          ;; => 2
5(count-matching #(> (:total-cents %) 5000)
6                [{:total-cents 3000}
7                 {:total-cents 9000}])    ;; => 1

The workflow stays the same. The behavior varies because pred varies.

In Java, you might reach for a strategy interface or a Predicate<T>. In Clojure, passing a function is the default move, not the advanced move.

Closures Let You Capture Configuration Cleanly

Higher-order code gets more useful when you return a function that remembers some configuration:

1(defn minimum-total [cents]
2  (fn [order]
3    (>= (:total-cents order) cents)))
4
5(filter (minimum-total 5000)
6        [{:id 1 :total-cents 3000}
7         {:id 2 :total-cents 9000}])
8;; => ({:id 2 :total-cents 9000})

The returned function closes over cents. That is a common functional pattern:

  • build the behavior once
  • reuse it across a pipeline
  • avoid creating a whole type just to hold configuration

Higher-Order Functions Are The Everyday Sequence Vocabulary

Much of idiomatic Clojure comes from a small family of higher-order tools:

  • map transforms each element
  • filter keeps elements that satisfy a predicate
  • remove drops elements that satisfy a predicate
  • reduce folds many values into one
  • sort-by uses a function to decide ordering

These are not “utilities off to the side.” They are the language you use to describe work over collections.

 1(require '[clojure.string :as str])
 2
 3(defn normalize-email [email]
 4  (-> email str/trim str/lower-case))
 5
 6(defn active-email-list [users]
 7  (->> users
 8       (filter :active?)
 9       (map :email)
10       (map normalize-email)
11       distinct))

The code reads as a transformation pipeline, not as a conversation among objects.

Functions Can Live In Data Structures Too

Another important step for Java developers is becoming comfortable with dispatch through data:

1(def renderers
2  {:json (fn [rows] {:content-type "application/json" :body rows})
3   :count (fn [rows] {:content-type "text/plain" :body (str (count rows))})})
4
5((get renderers :count) [{:id 1} {:id 2}])
6;; => {:content-type "text/plain", :body "2"}

This can replace a surprising amount of lightweight polymorphism. If the set of behaviors is small and explicit, a map of functions is often simpler than a hierarchy of types.

First-Class Functions Change API Design

Once you internalize that functions are values, your API design shifts:

  • pass behavior instead of subclassing for variation
  • return functions when configuration should be captured once
  • compose functions instead of binding behavior to object identity

This is one reason Clojure code often feels smaller than equivalent Java code. The language gives you a cheap way to parameterize behavior without creating a new class every time.

A Practical Warning

Higher-order programming helps when it makes variation explicit. It hurts when it turns a simple rule into an unreadable nest of anonymous functions.

Good practice:

  • name reusable behavior
  • keep anonymous functions small
  • favor clarity over abstraction density

If a higher-order API makes the call site harder to understand than the duplicated code would have been, step back and simplify.

Knowledge Check: Functions As Values

### What makes functions “first-class” in Clojure? - [x] You can pass them as arguments, return them, and store them like any other value. - [ ] They must be defined before any other code in a file. - [ ] They can only be used inside `map`. - [ ] They can’t close over values. > **Explanation:** In Clojure, functions are ordinary values, so higher-order programming becomes a normal part of API design rather than a special feature. ### What does this evaluate to? ```clojure (defn apply-twice [f x] (f (f x))) (apply-twice inc 5) ``` - [x] `7` - [ ] `6` - [ ] `5` - [ ] It throws because `inc` can’t be passed as a value. > **Explanation:** `inc` is passed as a normal value and invoked twice: `5 -> 6 -> 7`. ### What is the main benefit of higher-order functions? - [x] They let you write reusable code by parameterizing behavior with functions. - [ ] They avoid the need for immutable data structures. - [ ] They eliminate recursion from the language. - [ ] They enforce object-oriented encapsulation. > **Explanation:** Parameterizing behavior with functions often replaces small strategy types and other scaffolding with a simpler, more direct design.
Revised on Friday, April 24, 2026