Use object pools sparingly in Clojure, mainly for expensive external resources rather than ordinary immutable values.
The object pool pattern keeps a reusable set of expensive resources so callers can borrow and return them instead of constructing new ones repeatedly. In Clojure, this pattern should be used much more narrowly than in older object-oriented systems.
That is because ordinary immutable values usually do not need pooling. Creating maps, vectors, sets, and small functions is usually not the kind of expensive resource problem that justifies a pool. Pools make more sense for external or heavyweight runtime resources such as:
Before discussing implementation, ask one question:
Am I pooling expensive external resources, or am I trying to optimize ordinary value creation?
If the answer is “ordinary value creation,” the pool is probably the wrong tool. In modern JVM and Clojure code, object pools are often an outdated optimization unless profiling proves otherwise.
When a pool is justified, the shape is usually simple: a queue of available resources plus clear borrow and return operations.
1(ns app.pool)
2
3(defonce connection-pool
4 (atom clojure.lang.PersistentQueue/EMPTY))
5
6(defn seed-pool! [connections]
7 (reset! connection-pool (into clojure.lang.PersistentQueue/EMPTY connections)))
8
9(defn borrow-connection! []
10 (let [conn (peek @connection-pool)]
11 (when-not conn
12 (throw (ex-info "No connections available" {:type ::pool-empty})))
13 (swap! connection-pool pop)
14 conn))
15
16(defn return-connection! [conn]
17 (swap! connection-pool conj conn))
This example is intentionally small. In production, real pools usually need more:
That is why many teams use battle-tested pools from the underlying library ecosystem instead of building custom ones.
Object pools look attractive because they promise performance. But in Clojure, a pool can easily create more complexity than value:
If the cost is really in network latency, disk access, or remote service time, pooling ordinary values will not help.
Prefer these defaults before building a custom pool:
This is one of the clearest examples of a classic GoF-style label that survives in Clojure only for a narrow subset of real problems.
The older Clojure discussion of pools sometimes leans on agents and queues. A queue is still a natural way to model available resources. An agent can coordinate asynchronous actions, but it is not automatically the best pool primitive.
Often an atom plus a queue is enough for a small controlled design. For more involved pooling, the better answer is frequently:
Choose the simplest mechanism that matches the actual lifecycle.
Ask these before accepting an object-pool design:
If those answers are not strong, the pool is probably unnecessary complexity.