Multiple dispatch (also called multimethods) is a feature of some programming languages in which a function or method's implementation is selected based on the runtime types or values of more than one of its arguments [1]. It generalizes the more familiar single dispatch model of conventional object-oriented programming, where method selection considers only one receiver. Multiple dispatch is the central organizing principle of object-oriented programming in Common Lisp's CLOS, Dylan, Julia, Cecil, and several other languages [2][3].
The mechanism addresses the expression problem that troubles both functional and conventional OO designs, and is especially valuable in scientific computing, symbolic computation, and AI knowledge representation [4][5]. Julia has popularized multiple dispatch outside the Lisp community by showing that specialization can make multimethods perform comparably to statically dispatched code [4].
The word "dispatch" refers to how a language runtime decides which concrete implementation of an operation to invoke. Disciplines differ in two axes: how many arguments participate, and whether the decision is made at compile time or run time.
obj.method(args) looks up method in a virtual table tied to the runtime class of obj. The remaining arguments do not affect which body runs.f(a, b, c) selects the most specific applicable method given the types of all arguments together.The term "multimethod" was popularized by CLOS in 1988, although the idea appeared earlier in Xerox PARC's CommonLoops experiment [8][9].
Consider a physics simulation in which two objects collide. The classes are Asteroid and Spaceship, and collide(x, y) produces a different effect depending on which kinds of object collide.
In a single-dispatch language this is awkward. The call x.collide(y) selects on the class of x only, so the method body must inspect y with instanceof or isinstance checks, or the programmer must encode a visitor pattern. Both approaches scale poorly as more classes are added.
In a multiple-dispatch language the four cases are written directly:
collide(::Asteroid, ::Asteroid) = "shatter both"
collide(::Asteroid, ::Spaceship) = "asteroid impacts hull"
collide(::Spaceship, ::Asteroid) = "asteroid impacts hull"
collide(::Spaceship, ::Spaceship) = "ships collide"
The runtime examines the types of both arguments and selects the matching method. No if ladder is needed; adding a new class such as SpaceStation only requires new method clauses. Multiple dispatch treats all arguments as equal participants in the dispatch decision.
In 1998 Phil Wadler popularized the term "expression problem" to describe a long-standing tension in language design [10]. The problem asks how to build a system in which programmers can (1) add new data types, (2) add new operations on those types, (3) without recompiling or modifying existing code, and (4) while retaining static type safety.
Conventional OO makes (1) easy and (2) hard: adding a subclass is local, but adding a new operation requires editing every class. Functional programming organized around algebraic data types and pattern matching has the opposite weakness: a new operation is just a new function, but a new constructor forces every existing function to add a clause.
Multiple dispatch makes both new types and new operations external to existing definitions. A new type can be introduced together with method clauses for any existing generic function, and a new generic function can be introduced together with methods covering any existing types. Neither extension requires modifying the original sources [4][10].
Several languages support multiple dispatch as a first-class feature; many others provide it through libraries.
| Language | Year | Mechanism | Notes |
|---|---|---|---|
| Common Lisp (CLOS) | 1988 | defgeneric, defmethod | Canonical multimethod system with method combination [2] |
| Dylan | 1992 | Generic functions and methods | Apple project; only OO model [11] |
| Cecil | 1992 | Multimethods, predicate guards | Research language by Craig Chambers [7] |
| Diesel | 2002 | Multimethods | Cecil's successor |
| Nice | 1999 | Statically typed multimethods | JVM bytecode |
| Julia | 2012 | Generic functions, JIT-specialized | Primary paradigm; fast via specialization [4] |
| Raku (Perl 6) | 2015 | multi sub, multi method | Native multi-dispatch on type and arity [12] |
| Clojure | 2007 | defmulti/defmethod | Dispatch on user-defined function [13] |
| Groovy | 2003 | Runtime overload resolution | Dynamic dispatch on runtime types |
| C# | 2010+ | dynamic keyword | Multimethod-like since 4.0 [14] |
| Python | n/a | multimethod, multipledispatch libs | functools.singledispatch is single-only |
| Haskell | 1990 | Type classes | Compile-time, parametric form |
| Tiny CLOS | 1992 | Library | CLOS-style multimethods for Scheme |
Elixir and Erlang dispatch function clauses by pattern matching on values, which is multi-argument and dynamic but not type-driven. Tim Sweeney's game language Skookumchuck, in his 2006 POPL keynote "The Next Mainstream Programming Language," also included multimethod dispatch [15].
The CLOS specification by Daniel Bobrow, Linda DeMichiel, Richard Gabriel, Sonya Keene, Gregor Kiczales, and David Moon introduced multimethods to a wide audience of Common Lisp programmers in 1988 [2]. CLOS treats methods not as belonging to a class but to a generic function, an object holding a collection of methods specialized on tuples of argument types.
The principal forms are defgeneric, which declares a generic function, and defmethod, which adds a single specialized clause. When the function is called, CLOS computes the applicable methods by checking each method's specializers against the actual argument classes, then orders them by specificity using each argument's class precedence list (CPL), a linearization of the multiple-inheritance graph. The most specific method runs first, and call-next-method chains to the next [16].
CLOS also supports method combination. Methods can be qualified :before, :after, or :around, which run unconditionally alongside the primary method. Custom combinations such as progn, +, min, max, append, and nconc let the language combine results from many methods, a feature often used in framework design. A logging :before method, for example, runs whenever any specialization of a generic function fires, without modifying the primary methods.
In Julia, designed by Jeff Bezanson, Stefan Karpinski, Viral Shah, and Alan Edelman, multiple dispatch is the only object-oriented mechanism the language provides [4]. There are no classes with bound methods. Every function is generic, and the runtime selects a method by examining the concrete types of all positional arguments.
Julia compiles each method specialization to native code on demand using its JIT compilation infrastructure built on LLVM. The first call to f(x::Int, y::Float64) triggers the compiler to generate a version specialized to those argument types. Later calls with the same signature reuse the compiled method directly, with no dispatch overhead beyond a cached method-table lookup. The dispatch decision is logically dynamic, but the executed code is essentially monomorphized [4].
Methods can be specialized on abstract types, concrete types, parametric types, and type unions. Dispatch operates on positional arguments only, not on keyword arguments, which keeps the method-table semantics tractable.
In his 2019 JuliaCon talk "The Unreasonable Effectiveness of Multiple Dispatch," Karpinski argued that multiple dispatch is unusually effective at enabling package composability [5]. Because methods belong to generic functions rather than classes, an unrelated package can extend an existing function with new methods for its own types. A DataFrame from one package accepts statistics from another, plotting from a third, and gradient-based optimization from a fourth, without any of those packages having anticipated the others. Karpinski emphasized that this was empirical: the community discovered that multiple dispatch reused existing definitions across packages far more often than the designers had expected.
The cost of dispatch depends on how much work the runtime does to pick a method.
CLOS implementations such as SBCL combine generic-function caches with type-discriminating networks, and Julia's specialization makes hot dispatch in numerical code competitive with hand-written C [4].
| Concept | When resolved | Selection criterion | Typical languages |
|---|---|---|---|
| Single dispatch | Run time | Type of one receiver | Smalltalk, Java, Python, Ruby |
| Multiple dispatch | Run time | Tuple of argument types | CLOS, Dylan, Julia |
| Predicate dispatch | Run time | Arbitrary boolean over arguments | Cecil, Diesel |
| Pattern matching | Compile or run time | Structural shape of values | Haskell, Scala, Rust, ML |
| Type classes / traits | Compile time | Constraints on type parameters | Haskell, Rust |
| Method overloading | Compile time | Static argument types | C++, Java, C# |
| Visitor pattern | Run time | Two indirections through single dispatch | Java, C++ |
defmulti (Clojure) | Run time | Result of arbitrary dispatch function | Clojure |
Multiple dispatch is dynamic, type-driven, and symmetric across arguments. Predicate dispatch is strictly more general but harder to implement efficiently. Pattern matching is comparably expressive but usually closed: each function lists clauses in one place, whereas multimethods can be extended externally. Type classes give similar extensibility at compile time but require the dispatched value's type to be known statically.
Multiple dispatch is a good fit for scientific computing, symbolic computation, and AI knowledge representation.
Numerical type promotion. Numerical libraries must handle every combination of argument types (Int + Float64, Rational + Complex, and so on). With multiple dispatch each combination is one method, and a standard-library promotion mechanism reduces N x N cases to a few base rules. The same is awkward in single-dispatch languages because either x or y must asymmetrically be the receiver.
Symbolic algebra. Computer algebra systems in Common Lisp, Dylan, and Julia express simplification rules as multimethods. simplify(::Sum, ::Sum) and simplify(::Product, ::Sum) are each separate methods, declared wherever the operator types are defined.
Composable scientific libraries. Julia's DifferentialEquations.jl, Flux.jl, and JuMP.jl interoperate without explicit adapters because they extend the same generic functions on shared abstract types. A custom AbstractMatrix subtype works with linear solvers, automatic differentiation, and plotting from independent packages [4][5].
AI knowledge representation. CLOS-based AI systems including Cyc and earlier expert system shells use multimethods for inference rules. Adding a new fact type only requires methods for rules that should observe it.
Domain-specific languages. Operator overloading combined with multiple dispatch makes it easy to extend arithmetic and comparison operators across user-defined types, heavily used in automatic differentiation, interval arithmetic, and units of measure.
Multiple dispatch comes with trade-offs that single-dispatch languages mostly avoid.
f(::A, ::AB) and f(::AB, ::A) can both match f(ab, ab). Julia raises a method-ambiguity error and asks for an explicit tie-breaker f(::AB, ::AB); CLOS uses the class precedence list but can still produce ambiguous orderings [16].methodswith and which, and CLOS environments offer similar tools, but newcomers find it harder to navigate large codebases.Multiple dispatch did not appear all at once. Its history runs through the Lisp family and several research projects of the 1980s.
Symbolics Flavors, the object system used on Lisp machines from the late 1970s, was strictly single-receiver but introduced influential ideas about generic functions, mixins, and method combinations [17]. New Flavors added "daemons" for before- and after-method combinations later generalized by CLOS.
In 1986 Daniel Bobrow and colleagues at Xerox PARC presented CommonLoops, the first widely circulated object system to dispatch on multiple arguments [9]. Two years later the merger of CommonLoops, Symbolics's New Flavors, and Lucid's ObjectLisp produced CLOS, codified by the ANSI Common Lisp standard [18]. Guy Steele's Common Lisp the Language (second edition, 1990) popularized the design [19].
By the early 1990s Apple had launched Dylan, a CLOS descendant influenced by writing the original Newton software in CLOS, and Craig Chambers at the University of Washington had begun designing Cecil, a research language using multimethods to investigate static analysis and predicate dispatch [3][7]. Through the 2000s, multiple dispatch was largely confined to research and to the Lisp/Dylan diaspora.
That changed in the 2010s with Julia, which made multiple dispatch the foundation of a numerically focused language with a working JIT compiler [4]. Julia has since become the largest user community for multiple dispatch, reviving interest in the Python scientific stack and Clojure.
Giovanni Castagna's 1995 paper "Covariance and contravariance: conflict without a cause" formalized how subtyping interacts with multimethod selection [6]. Scheme has long supported library-level multimethods through Tiny CLOS [20]; the homoiconicity of Lisp dialects made it easy to retrofit multimethod systems as user-level macros, and earlier work by Steele and Gerald Sussman on first-class procedures laid groundwork for the study of generic functions.
The following fragments express the same collide(a, b) multimethod in several languages.
Common Lisp / CLOS.
(defgeneric collide (a b))
(defmethod collide ((a asteroid) (b asteroid)) "shatter both")
(defmethod collide ((a asteroid) (b spaceship)) "asteroid impacts hull")
(defmethod collide ((a spaceship) (b asteroid)) "asteroid impacts hull")
(defmethod collide ((a spaceship) (b spaceship)) "ships collide")
Clojure, dispatching on a vector of class symbols.
(defmulti collide (fn [a b] [(class a) (class b)]))
(defmethod collide [Asteroid Spaceship] [_ _] "asteroid impacts hull")
(defmethod collide [Spaceship Spaceship] [_ _] "ships collide")
Python with the third-party multimethod package.
from multimethod import multimethod
@multimethod
def collide(a: Asteroid, b: Spaceship): return "asteroid impacts hull"
@multimethod
def collide(a: Spaceship, b: Spaceship): return "ships collide"
C# uses the dynamic keyword to defer overload resolution to run time, the idiomatic multimethod-like dispatch in C# 4.0 and later [14].
string Collide(Asteroid a, Spaceship b) => "asteroid impacts hull";
string Collide(Spaceship a, Spaceship b) => "ships collide";
dynamic x = ..., y = ...;
var result = Collide(x, y); // resolved at run time on actual types
Raku (Perl 6).
multi collide(Asteroid $a, Spaceship $b) { "asteroid impacts hull" }
multi collide(Spaceship $a, Spaceship $b) { "ships collide" }
All forms compile to a runtime selection on the types of both arguments. They differ in syntax and in the dispatch machinery but share the same conceptual content.