100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
Programming

Multimethods and Protocols

Explore Clojure's two mechanisms for open, extensible polymorphism — arbitrary-dispatch multimethods and fast, type-based protocols — and learn when to use each.

Functional ProgrammingAdvanced11 min readJul 10, 2026
Analogies

Polymorphism Beyond Class Hierarchies: defmulti and defmethod

Clojure's multimethods provide open, extensible polymorphism based on an arbitrary dispatch function rather than a single argument's class, as in traditional OO. (defmulti area :shape) declares that calling area will look up the :shape key of its argument, and each (defmethod area :circle [s] ...) registers an implementation for a specific dispatch value; a :default method catches anything unmatched. This lets you dispatch on multiple arguments, on value combinations, or on arbitrary computed criteria, not just a single object's runtime type.

🏏

Cricket analogy: An umpire's decision procedure that dispatches on the specific appeal type, LBW versus caught versus run-out, to a different rulebook check for each is exactly a multimethod dispatching on a keyword.

clojure
(defmulti area :shape)
(defmethod area :circle [{:keys [radius]}] (* Math/PI radius radius))
(defmethod area :rectangle [{:keys [w h]}] (* w h))
(defmethod area :default [s] (throw (ex-info "Unknown shape" {:shape s})))

(area {:shape :circle :radius 2}) ;=> 12.566370614359172

Dispatch Hierarchies with derive

Multimethods can dispatch not just on exact keyword matches but on ad-hoc hierarchies built with derive, which lets you declare that :square derives from ::shape or that ::square derives from ::rectangle; defmethod implementations then match the most specific ancestor available, and prefer-method resolves ambiguity when a value matches two unrelated parents. This gives you inheritance-like reuse without tying your data to a fixed class hierarchy.

🏏

Cricket analogy: Classifying a golden duck as a specific kind of duck, which is itself a specific kind of dismissal, lets a stats engine reuse the general dismissal-handling logic while still recognizing the more specific case — exactly like derive's hierarchy.

clojure
(derive ::square ::rectangle)
(defmethod area ::rectangle [{:keys [w h]}] (* w h))
(area {:shape ::square :w 4 :h 4}) ;; resolves via the ::rectangle method => 16

Protocols: Fast, Type-Based Polymorphism

defprotocol defines a named set of function signatures that types can implement, similar to a Java interface but usable with Clojure's own types, records, and even existing classes; extend-type or extend-protocol wire an implementation onto a specific type after the fact, without needing to modify the original type's source. Protocols dispatch on the runtime type of the first argument alone and compile to efficient JVM interface calls, making them significantly faster than multimethods for the common single-argument, type-based dispatch case — the trade-off is losing multimethods' arbitrary dispatch-function flexibility.

🏏

Cricket analogy: A standardized 'how to dismiss' interface that every bowler type, pacer, spinner, or all-rounder, implements in their own specific way, but always exposes the same attempt_dismissal signature, mirrors defprotocol's fixed function set with type-specific implementations.

clojure
(defprotocol Shape
  (perimeter [this]))

(defrecord Circle [radius]
  Shape
  (perimeter [this] (* 2 Math/PI radius)))

(defrecord Square [side]
  Shape
  (perimeter [this] (* 4 side)))

(perimeter (->Circle 3)) ;=> 18.84955592153876

Choose protocols when dispatch is purely on the type of the first argument and performance matters; choose multimethods when you need to dispatch on arbitrary logic, multiple arguments, or a value's data rather than its concrete type.

extend-type and extend-protocol let you add protocol implementations to types you don't own, including Java classes, but silently redefining an implementation for a type that already has one will overwrite it without warning — be careful when multiple namespaces extend the same protocol to the same type.

  • defmulti declares a dispatch function; defmethod registers an implementation for a specific dispatch value.
  • Multimethods can dispatch on arbitrary computed values, multiple arguments, or ad-hoc hierarchies, not just a single argument's type.
  • derive builds custom hierarchies so defmethod implementations can match ancestor relationships, with prefer-method resolving ambiguity.
  • defprotocol defines a named set of function signatures; extend-type or extend-protocol attach implementations to existing types.
  • Protocols dispatch on the runtime type of the first argument and compile to fast JVM interface calls.
  • Choose protocols for performance-sensitive, single-argument type-based dispatch; choose multimethods for flexible, arbitrary dispatch logic.
  • Both mechanisms provide open, extensible polymorphism without requiring you to modify a type's original definition.

Practice what you learned

Was this page helpful?

Topics covered

#Programming#ClojureStudyNotes#MultimethodsAndProtocols#Multimethods#Protocols#Polymorphism#Beyond#StudyNotes#SkillVeris#ExamPrep