Multiton Pattern and Dynamic Vars in Clojure

Learn what a multiton really is in Clojure, why a keyed registry is usually the pattern's core, and how dynamic vars can help with scoped selection but should not be mistaken for the registry itself.

Multiton pattern: A keyed registry that manages a bounded set of distinct shared instances instead of one global singleton.

The old object-oriented explanation of multiton is straightforward: instead of one global instance, you keep one instance per key. The part that needs correction in Clojure is the role of dynamic vars. Dynamic vars are useful for scoped contextual selection, but they are usually not the multiton itself.

The Registry Is the Core Pattern

In Clojure, a simple multiton usually looks like a registry or lifecycle-managed map:

 1(defonce instances (atom {}))
 2
 3(defn create-instance [k]
 4  {:instance/key k
 5   :created-at (System/currentTimeMillis)})
 6
 7(defn get-instance [k]
 8  (or (get @instances k)
 9      (let [instance (create-instance k)]
10        (get (swap! instances #(if (contains? % k) % (assoc % k instance))) k))))

That is the essence of multiton:

  • one shared instance per key
  • centralized lookup
  • controlled creation

Dynamic Vars Solve a Different Problem

Dynamic vars are most useful when code needs a scoped notion of the “current” instance for this thread or request.

1(def ^:dynamic *current-tenant* nil)
2
3(defn current-instance []
4  (when *current-tenant*
5    (get-instance *current-tenant*)))
6
7(binding [*current-tenant* :tenant-a]
8  (current-instance))

Here the dynamic var does not store the registry. It stores the current key selection for a scoped execution context.

That is a much better fit for dynamic vars:

  • request-scoped config
  • current tenant selection
  • temporary test bindings
  • per-thread contextual behavior

Do Not Use Dynamic Vars as a Shared Mutable Registry

That design confuses two very different concerns:

  • the durable keyed store of instances
  • the scoped current binding used during one execution path

Dynamic vars are not a substitute for explicit lifecycle management. They are a contextual-selection tool.

Multiton Needs Lifecycle Thinking

If the instances are expensive resources such as:

  • database connections
  • service clients
  • tenant-specific configuration objects
  • compiled templates or caches

then the registry also needs:

  • creation policy
  • shutdown or cleanup policy
  • concurrency safety
  • bounds on growth

Otherwise multiton quietly turns into a resource leak with a nice pattern name.

Bounded Keys Matter More Than Clever Lookup

The health of a multiton often depends less on the lookup function and more on whether the key space is intentionally bounded. Tenant identifiers, region names, or a short list of service roles are manageable. User IDs, request IDs, or arbitrary payload-derived keys usually are not.

When the key space can grow, the design needs one of these answers:

  • explicit eviction rules
  • lifecycle hooks that dispose of stale resources
  • startup-time registration of the only valid keys
  • a different design where instances are passed explicitly instead of cached globally

Common Failure Modes

Treating Dynamic Vars as the Registry

This mixes scoped binding with shared instance management and makes the design harder to reason about.

Building an Unbounded Global Instance Cache

If keys are not bounded or lifecycle is ignored, memory and resource use can drift upward indefinitely.

Choosing Multiton for Data That Should Just Be Passed Explicitly

Sometimes a registry is unnecessary and explicit dependency passing is clearer.

Practical Heuristics

Use a multiton when the system genuinely needs one shared instance per meaningful key. Represent that with an explicit registry. Use dynamic vars only when code also needs a scoped current selection such as the active tenant or environment. In Clojure, multiton works best when the registry and the scoped binding are treated as related but distinct tools.

Ready to Test Your Knowledge?

Loading quiz…
Revised on Thursday, April 23, 2026