Reuse behavior by composing small functions and data transformations instead of coupling logic to class hierarchies.
Java developers often learn reuse through types: interfaces, abstract classes, decorators, strategies, service objects, and dependency injection containers. Those tools can work well, but they also create pressure to solve every variation with more structure.
Clojure takes a different path. Reuse usually starts with:
The result is modularity with less scaffolding. You spend less effort deciding where a behavior “belongs” in a class hierarchy and more effort deciding which transformations should be combined.
If behavior is a value, you can pass it into a general workflow instead of inventing a new type to host it.
1(defn import-users [parse-row valid? enrich rows]
2 (->> rows
3 (map parse-row)
4 (filter valid?)
5 (map enrich)))
Now the variability is explicit:
parse-row decides how a row becomes a mapvalid? decides which rows surviveenrich decides what additional fields to computeThe workflow stays the same while the behavior is swapped at the call site.
1(import-users csv->user user-valid? attach-default-flags rows)
2(import-users api-payload->user partner-user-valid? attach-source-tag payloads)
That is the functional version of a strategy pattern, but without extra ceremony.
Clojure namespaces still matter. They are the unit of organization and API exposure. But within a namespace, reuse often looks like composing transformations:
1(ns my.app.pricing)
2
3(defn apply-discount [order]
4 (update order :total-cents - (:discount-cents order 0)))
5
6(defn apply-tax [order]
7 (update order :total-cents + (:tax-cents order 0)))
8
9(defn finalize-order [order]
10 (-> order
11 apply-discount
12 apply-tax
13 (assoc :status :ready)))
Each helper is independently testable. finalize-order is reusable because it describes the order of the transformations clearly, not because it inherits behavior from a base type.
In Java, interfaces are often used to create a common contract among several implementations. In Clojure, a simple map shape is often enough:
1{:id 42
2 :plan :team
3 :status :trial
4 :discount-cents 500}
If multiple functions agree on that data shape, you already have a useful contract. You may still document it, test it, or validate it with clojure.spec or Malli later. The key point is that the contract is expressed through data and behavior, not only through a type declaration.
Functional design does not mean “never use dispatch.” It means “use dispatch when the problem truly requires it.”
Reach for protocols or multimethods when:
case or cond branches is becoming noisyDo not reach for them just because a Java design would start with an interface. Many workflows are clearer when they stay as ordinary functions over maps.
For Java engineers moving into Clojure, the most valuable question changes from:
“Which class should own this behavior?”
to:
“Which transformations are stable enough to name and compose?”
That shift matters because composition is cheaper than inheritance-heavy reuse:
The common mistake is replacing class-heavy design with anonymous-function-heavy design that nobody can read. Reusable Clojure code still needs:
If a composed workflow is hard to explain, it is not reusable yet. Name the pieces first. Then compose them.