Understand what it means for functions to be values in Clojure, why that matters for Java developers, and how it leads directly to higher-order programming.
When Clojure says functions are first-class, it means they can be treated like values.
That sounds abstract until you compare it with everyday Java design. In Java, behavior often lives in methods, classes, and interfaces. In Clojure, behavior can be passed around directly as a function value.
That one shift is what makes the rest of this chapter possible.
First-class function: A function value you can store, pass to another function, or return from a function.
If a function is first-class, you can do all of these things with it:
| Operation | Clojure example | Why it matters |
|---|---|---|
| Bind it to a name | (def normalize clojure.string/trim) |
Behavior can be named and reused |
| Put it in data | {:formatter pr-str} |
Configuration can carry behavior |
| Pass it to another function | (map inc [1 2 3]) |
Collection processing becomes generic |
| Return it from a function | (defn adder [n] (fn [x] (+ x n))) |
You can build behavior from smaller pieces |
This is not a niche feature. It is the reason Clojure code naturally gravitates toward pipelines, transformation functions, and composition instead of class hierarchies full of tiny strategy objects.
Java developers usually hear “functions are first-class” and think of lambdas.
That is directionally useful, but it hides an important difference:
| Java shape | Clojure shape |
|---|---|
| Methods live on classes or objects | Functions are ordinary values created with fn or defn |
Passing behavior usually involves a functional interface such as Function<T,R> or Predicate<T> |
Passing behavior usually means passing the function directly |
| Method references and lambdas reduce ceremony, but methods themselves are not free-floating function values | A function value is already the normal unit of behavior |
The official Clojure functions guide puts this bluntly: Java methods are not Clojure functions. If you want method behavior as a function, you wrap or reference it in function form.
fn, def, and defnOne point matters more in Clojure than it does in Java:
fn creates a function objectdef interns a var in the current namespacedefn is the usual way to define a named function by creating a var whose value is a function1(def formatter
2 (fn [customer]
3 (str (:customer/id customer) ":" (:customer/email customer))))
4
5(defn premium? [customer]
6 (= :premium (:customer/tier customer)))
In day-to-day code, you mostly treat formatter and premium? as callable functions. Under the hood, the namespace stores them in vars. That distinction matters later for REPL work and redefinition, but the immediate lesson is simpler:
functions are real values, not special syntax that only works in one place.
Once functions are values, design pressure shifts.
Instead of asking:
you start asking:
That is a much better fit for data-heavy code.
Suppose you need to total orders using different pricing rules.
1(defn subtotal [{:order/keys [items]}]
2 (reduce (fn [sum {:line/keys [price quantity]}]
3 (+ sum (* price quantity)))
4 0M
5 items))
6
7(defn total-by [pricing-fn orders]
8 (reduce (fn [sum order]
9 (+ sum (pricing-fn order)))
10 0M
11 orders))
Now you can vary behavior by passing a function:
1(defn list-price [order]
2 (subtotal order))
3
4(defn discounted-price [order]
5 (* 0.90M (subtotal order)))
6
7(total-by list-price orders)
8(total-by discounted-price orders)
No inheritance is required. No interface is required. The varying policy is visible in the function argument.
That visibility is one of the biggest benefits of first-class functions: code tells you where the variability really is.
map, filter, and reduceHigher-order functions are just functions that consume or produce other functions.
If “function as value” still feels unusual, then map, filter, sort-by, comp, partial, and keep can look magical.
Once “function as value” feels normal, those tools become straightforward:
map takes a transformation functionfilter takes a predicate functionsort-by takes a key-extraction functionThat is why this concept is foundational instead of optional background theory.
Java developers often make one of these mistakes early:
| Mistake | Better mental model |
|---|---|
| “A function is basically a method with lighter syntax.” | A function is a value you can compose, store, and pass around directly |
| “Higher-order functions are advanced FP tricks.” | They are ordinary functions whose input or output includes other functions |
| “I should wrap every varying behavior in a custom abstraction immediately.” | First identify the single piece of behavior that varies, then pass just that function |