Protocols for Polymorphism in Clojure

Learn when protocols are the right polymorphism tool in Clojure, how they differ from multimethods and records alone, and how to extend them safely.

Protocol: A named set of method signatures that dispatch on the type of the first argument, giving Clojure a high-performance, open-ended way to express interface-style polymorphism.

Protocols matter because they let you describe an abstraction without forcing everything into an inheritance hierarchy. They are a strong fit when the main dispatch question is simple type-based behavior and performance or interop matters.

What Protocols Are Good At

Protocols work best for the common case of single dispatch on type. They give you:

  • interface-style abstraction
  • fast dispatch
  • the ability for one type to implement multiple protocols
  • the ability to extend existing types later

That last point is important. According to the official docs, protocols were designed partly to avoid the problem where only the original type author gets to decide which interfaces a type implements.

Defining a Protocol

1(defprotocol Shape
2  (area [shape])
3  (perimeter [shape]))

This creates:

  • a protocol object
  • a set of polymorphic functions
  • a generated interface for host interop

The main semantic rule is that dispatch happens on the type of the first argument.

Implementing Protocols on Your Own Types

Protocols pair naturally with records and types:

1(defrecord Rectangle [width height]
2  Shape
3  (area [_] (* width height))
4  (perimeter [_] (* 2 (+ width height))))

This is a clean fit when you control the type and the behavior genuinely belongs with that representation.

Extending Existing Types

One of the real strengths of protocols is that you can extend a type later:

1(extend-type java.lang.String
2  Shape
3  (area [s] (count s))
4  (perimeter [s] (* 2 (count s))))

This is more flexible than host-language interfaces, but it comes with a design warning. The official protocol reference explicitly advises caution when you own neither the protocol nor the type. In library code, that can create conflicts. In app code, it is usually more acceptable because you control the local environment.

Protocols vs Multimethods

A common mistake is reaching for protocols when the dispatch rule is not really about type.

Use a protocol when:

  • dispatch is on the type of the first argument
  • performance matters
  • the abstraction feels interface-like

Use a multimethod when:

  • dispatch depends on values, keywords, or combinations of attributes
  • the choice is not fundamentally about the host type
  • you need more flexible dispatch logic than simple type-based behavior

Protocols are not a replacement for every form of polymorphism.

Protocols Are Not Just Fancy Maps of Functions

You can absolutely write ordinary functions that dispatch on keys or values manually, and sometimes that is the better call. Protocols earn their keep when the abstraction is stable enough to deserve a named interface and the participating types are genuinely different implementations of that interface.

That means protocols are often strong for:

  • serialization targets
  • storage adapters
  • rendering strategies
  • domain entities with type-based behavior

They are weaker when the behavior is just a few branches over ordinary data values.

    flowchart TD
	    A["Need polymorphism"] --> B{"Dispatch mainly on type of first argument?"}
	    B -- Yes --> C["Protocol is a strong candidate"]
	    B -- No --> D["Consider multimethods or ordinary functions"]

Design Protocols Narrowly

Protocols work best when the methods belong together conceptually.

Bad protocol design often looks like:

  • too many unrelated methods
  • “god protocols” that every type must partially fake
  • abstractions shaped around one implementation instead of the shared concept

A good protocol usually answers one clear question, such as “how does this thing render?” or “how do I persist this target?”

Common Mistakes

  • using protocols when dispatch is really on values rather than types
  • designing huge protocols with unrelated methods
  • extending a protocol in library code when you own neither the protocol nor the target type
  • using a protocol where ordinary data-oriented functions would be simpler
  • assuming protocols and multimethods solve the same design problem

Key Takeaways

  • Protocols are for fast, first-argument, type-based polymorphism.
  • They pair naturally with records, types, and extension of existing classes.
  • They are best when the abstraction feels interface-like and stable.
  • Multimethods are better when dispatch depends on values or more complex rules.
  • Extend with care when you do not own both sides of the relationship.

Ready to Test Your Knowledge?

Loading quiz…
Revised on Thursday, April 23, 2026