Common Lisp Object System (CLOS)
Last reviewed
May 2, 2026
Sources
13 citations
Review status
Source-backed
Revision
v1 · 3,646 words
Improve this article
Add missing citations, update stale details, or suggest a clearer explanation.
Last reviewed
May 2, 2026
Sources
13 citations
Review status
Source-backed
Revision
v1 · 3,646 words
Add missing citations, update stale details, or suggest a clearer explanation.
The Common Lisp Object System (CLOS, often pronounced "see-loss" or "kloss") is the object-oriented programming facility built into ANSI Common Lisp. It was added to the language in 1988 by the X3J13 standards committee and integrated into ANSI INCITS 226-1994 (formerly ANSI X3.226-1994), the formal Common Lisp standard. Unlike most object systems of its era, CLOS is built around generic functions and multiple dispatch rather than message passing to a privileged receiver, supports multiple inheritance with a deterministic class precedence list, allows complex method combinations (including before, after, and around methods), and exposes a Metaobject Protocol that lets programmers reach into and modify the implementation of the object system itself.
CLOS grew out of the Lisp object systems used in 1980s artificial intelligence research, especially Flavors and New Flavors at the MIT AI Lab and Symbolics, and CommonLoops at Xerox PARC. It influenced later languages with multiple dispatch, including Dylan, Julia, and the multimethods in Clojure. Gregor Kiczales, one of CLOS's principal authors, would go on from work on the Metaobject Protocol to develop aspect-oriented programming and AspectJ at Xerox PARC in the late 1990s.
A CLOS program is built from four kinds of entities: classes, slots, generic functions, and methods. Classes describe the shape of objects (their slots, which are roughly equivalent to fields in other languages). Generic functions are named operations whose actual behavior depends on the classes of all of their arguments, not just the first one. Methods are pieces of code attached to a generic function and specialized on the classes of one or more arguments. When a generic function is called, CLOS picks the applicable methods, sorts them by specificity, and combines them into an effective method using a method combination strategy.
This is a different shape from the more common object model in Smalltalk, C++, Python, or Java, where a method belongs to a class and is invoked by sending a message to an object: obj.method(args). In CLOS, a generic function is a stand-alone function. There is no receiver. Calling (area shape) looks up the most specific applicable method based on the class of shape; calling (intersect circle rectangle) looks it up based on the classes of both circle and rectangle. The dispatch rule is symmetric across arguments.
CLOS is part of the standard. Any conforming Common Lisp implementation provides it, including SBCL, Clozure CL (CCL), ECL, ABCL, CLISP, GCL, LispWorks, and Allegro Common Lisp. Historically it was also available on Symbolics Genera and on Lucid Common Lisp.
Lisp had been used for object-oriented programming since the 1970s. The most influential of the early systems was Flavors, developed by Howard Cannon at the MIT Artificial Intelligence Laboratory for the Lisp Machine and Lisp Machine Lisp. Flavors was the first widely used language to feature mixins. It used message passing in the Smalltalk style, but with multiple inheritance, before-daemons and after-daemons, and other features that were unusual for their time. Symbolics adopted Flavors and built much of the Genera operating system on top of it.
Xerox PARC, in parallel, was experimenting with LOOPS (Lisp Object-Oriented Programming System) and then CommonLoops, presented at OOPSLA in 1986 by Daniel G. Bobrow, Kenneth Kahn, Gregor Kiczales, Larry Masinter, Mark Stefik, and Frank Zdybel. CommonLoops is where the generic function model and the heavy use of metaobjects (objects representing classes, methods, and dispatchers) really took shape. Generic functions are simply Lisp functions; there is no separate message-send protocol.
Between Flavors and CommonLoops sat New Flavors, a redesign of Flavors that replaced message passing with generic functions. This was the bridge: it convinced the Lisp Machine community that giving up the receiver-based model in favor of generic functions was workable.
In 1986, the groups behind New Flavors and CommonLoops met to merge their designs into a single proposal for the X3J13 Common Lisp standardization committee. The principal authors of the resulting CLOS specification were Daniel G. Bobrow and Gregor Kiczales (both Xerox PARC), David A. Moon and Sonya E. Keene (both Symbolics), and Richard P. Gabriel and Linda G. DeMichiel (both Lucid). The early specification appeared as X3J13 Document 87-002 in 1987. The full specification, often referenced today, was X3J13 Document 88-002R, in three parts: Part 1 covered Programmer Interface Concepts, Part 2 the Functions in the Programmer Interface, and Part 3 the Metaobject Protocol.
At its meeting on June 15, 1988, X3J13 voted to adopt Parts 1 and 2 of the CLOS specification (the user-level language) into the draft Common Lisp standard. Part 3, the Metaobject Protocol, was deliberately left out of the standard but kept available as a de facto extension implemented by most of the major vendors. The full ANSI Common Lisp standard, INCITS 226-1994 (X3.226-1994), included the CLOS programmer interface as a normative part of the language.
In 1991, Kiczales, Jim des Rivières, and Bobrow published The Art of the Metaobject Protocol (MIT Press, ISBN 0-262-11158-6 hardcover, 0-262-61074-4 paperback). The book set out a formal specification of the Metaobject Protocol for CLOS and argued, more broadly, that exposing the design of an object system through reflective hooks is a useful programming language design technique in its own right. Sonya Keene's earlier Object-Oriented Programming in Common Lisp: A Programmer's Guide to CLOS (Addison-Wesley, 1989, ISBN 0-201-17589-4) remains the standard tutorial.
Classes are defined with defclass. A class has a name, a list of direct superclasses, and a list of slot specifiers. Slots can have initial values, types, accessor functions, allocation modes (per-instance or per-class), and documentation. Slot accessors come in three flavors: a :reader defines a function that reads the slot, a :writer defines a function that writes it, and an :accessor defines both with the read function and a (setf accessor) method.
(defclass animal ()
((name :initarg :name :reader animal-name)
(mass :initarg :mass :accessor animal-mass)))
(defclass mammal (animal) ())
(defclass dog (mammal)
((breed :initarg :breed :accessor dog-breed)))
Classes are themselves first-class objects, instances of a metaclass (standard-class by default). The default base class is standard-object, which in turn inherits from t, the universal class. Almost every Common Lisp built-in type is also a class: integer, string, symbol, and so on are classes you can specialize methods on.
A generic function is declared with defgeneric and acquires methods through defmethod.
(defgeneric describe-animal (animal))
(defmethod describe-animal ((a animal))
(format t "~a weighs ~a kg.~%" (animal-name a) (animal-mass a)))
(defmethod describe-animal ((d dog))
(call-next-method)
(format t "It is a ~a.~%" (dog-breed d)))
The syntax ((a animal)) is a parameter specializer: this method applies when the first argument is an instance of animal. The dog method is more specific. Calling (describe-animal fido) invokes the more specific method, and call-next-method runs the next applicable method in the precedence order, much like a super call but generalized to arbitrary chains of methods.
The big difference from Smalltalk-style object systems is that any method can specialize on more than one argument, and CLOS dispatches on all of them. A method definition like
(defmethod collide ((a asteroid) (s spaceship))
...)
(defmethod collide ((s spaceship) (a asteroid))
...)
(defmethod collide ((a asteroid) (b asteroid))
...)
defines three different methods on the same collide generic function, each picked based on the runtime classes of both arguments. This is what is meant by multiple dispatch. It is genuinely symmetric: there is no privileged "this" or "self".
Because methods belong to generic functions rather than to classes, there is also no syntactic distinction between "my class's method" and "someone else's method". Anyone can extend a generic function with new methods on their own classes, including methods that specialize on classes from third-party libraries. There is no private or protected; CLOS does not provide encapsulation by default. Slot access can be restricted by package conventions but is not enforced by the language.
When several methods are applicable to a call, CLOS combines them into an effective method according to a method combination type. The default is standard method combination, which uses four method qualifiers.
| Qualifier | Role | Order |
|---|---|---|
:before | Side-effecting setup | All applicable, most specific first |
| primary (no qualifier) | The actual computation | Most specific runs; call-next-method continues the chain |
:after | Side-effecting teardown | All applicable, most specific last |
:around | Wraps everything | Most specific runs first; usually delegates with call-next-method |
The semantics: all :before methods run, in most-specific-first order. Then the most specific primary method runs (and may call call-next-method to invoke less specific primaries). Then all :after methods run, in most-specific-last order. The value of the primary method becomes the value of the call. If :around methods exist, the most specific :around runs first and gets full control: only when it calls call-next-method do the other methods run at all.
Beyond standard method combination, CLOS provides several short method combinations: +, and, or, list, append, nconc, min, max, progn. With + combination, for example, a call returns the sum of the values returned by the applicable methods. Programmers can also define entirely new method combinations with define-method-combination, which gives the language something close to user-defined operator overloading at the dispatch level.
CLOS supports multiple inheritance. When a class has more than one direct superclass, CLOS computes a class precedence list (CPL) that totally orders the class and all its ancestors. The CPL determines method specificity, slot inheritance, and the order in which call-next-method walks through methods.
The CLOS standard specifies an algorithm for computing the CPL that is essentially what was later renamed C3 linearization. The C3 algorithm itself was described in a 1996 paper by Kim Barrett, Bob Cassels, Paul Haahr, David A. Moon, Keith Playford, and P. Tucker Withington in the context of Dylan, formalizing the same intuition: a class always precedes its direct superclasses, and the order in which superclasses are listed in defclass is preserved (the local precedence order). C3 was later adopted as the default method resolution order in Python 2.3 (2003) and in Raku, among others.
The CPL is what makes diamond inheritance well-behaved in CLOS. Slots inherited along multiple paths are merged according to documented rules (most specific writer wins for :initform, all type constraints are intersected, and so on).
Instances are created with (make-instance 'class-name :slot-name value ...). CLOS exposes the construction process as a chain of generic functions, each of which can be specialized: make-instance calls allocate-instance to create the storage, then initialize-instance, which by default calls shared-initialize. There is also reinitialize-instance for resetting slots after creation.
When a class is redefined while instances exist (a common situation in interactive Lisp development, since you are usually editing live code), CLOS does not invalidate the old instances. Instead, when you next touch one, CLOS calls update-instance-for-redefined-class, a generic function the programmer can specialize, to migrate the instance to the new shape. The same mechanism, via update-instance-for-different-class, supports change-class, which transmutes an existing instance into a member of a different class without copying.
The CLOS Metaobject Protocol (MOP) treats the building blocks of CLOS itself, classes, slot definitions, generic functions, and methods, as instances of metaclasses. Programmers can subclass standard-class, standard-generic-function, or standard-method and override the generic functions that the implementation uses internally to lay out instances, dispatch calls, compute method applicability, walk the class precedence list, and so on.
This is not a curiosity. The MOP is how a CLOS programmer adds capabilities the language designers never anticipated, without forking the implementation. Common uses include:
standard-class to intercept slot reads and writes, transparently mapping objects to a database or to disk.The MOP was deliberately kept out of ANSI Common Lisp because it was considered too implementation-sensitive to standardize. In practice, every major Common Lisp implementation supports the MOP described in The Art of the Metaobject Protocol, with small portability gaps that the closer-mop library smooths over.
The MOP is reflective in the strict sense: the same generic functions you use to define ordinary classes are also the ones that the system uses internally. Calling (class-of obj) gives you a class object you can introspect. Calling (class-of (class-of obj)) gives you the metaclass. The hierarchy bottoms out: standard-class is itself an instance of standard-class, so the tower does not require infinite metalevels.
What is unusual about CLOS, compared to languages like Java or Python where reflection is bolted on, is that the reflective interface and the user-facing interface are the same. Specializing compute-applicable-methods does not require any special hook; it is just a method definition.
A small expression tree using inheritance, accessors, and multiple dispatch:
(defgeneric evaluate (expr env))
(defclass expression () ())
(defclass literal (expression)
((value :initarg :value :reader literal-value)))
(defclass variable (expression)
((name :initarg :name :reader variable-name)))
(defclass binary-op (expression)
((left :initarg :left :reader op-left)
(right :initarg :right :reader op-right)))
(defclass plus (binary-op) ())
(defclass times (binary-op) ())
(defmethod evaluate ((e literal) env)
(declare (ignore env))
(literal-value e))
(defmethod evaluate ((e variable) env)
(cdr (assoc (variable-name e) env)))
(defmethod evaluate ((e plus) env)
(+ (evaluate (op-left e) env)
(evaluate (op-right e) env)))
(defmethod evaluate ((e times) env)
(* (evaluate (op-left e) env)
(evaluate (op-right e) env)))
(defmethod evaluate :before ((e expression) env)
(declare (ignore env))
(format *trace-output* "Evaluating ~s~%" e))
The :before method runs before every call to evaluate and traces the expression. Because it specializes on the root class expression, it applies uniformly to all subclasses. To turn tracing off, you remove that one method; you do not have to change any of the primary methods.
| Aspect | Smalltalk-style OOP (Smalltalk, Java, Python, Ruby, C++) | CLOS |
|---|---|---|
| Method dispatch | Single dispatch on receiver | Multiple dispatch on all specialized arguments |
| Method ownership | Methods belong to classes | Methods belong to generic functions |
| Receiver concept | self / this is privileged | Symmetric across arguments |
| Inheritance | Single (Java, Smalltalk) or multiple (C++, Python, Eiffel) | Multiple, with C3-style class precedence list |
| Combining inherited methods | super call | call-next-method, plus before/after/around qualifiers, plus user-defined method combinations |
| Encapsulation | Per-class private/protected/public | None enforced; convention only |
| Class as object | Varies (rich in Smalltalk and Python, weak in Java and C++) | Yes: classes are first-class instances of metaclasses |
| Reflection | Often bolt-on, separate API | Same API as the user-facing language; the MOP is just CLOS |
| Adding methods to existing classes | Often forbidden or awkward | Routine; methods can specialize on built-in or third-party classes |
The absence of encapsulation is a real design choice and not an oversight. CLOS treats classes as data definitions and generic functions as operations on data; it does not bundle them. This is closer in spirit to abstract data types in functional languages than to message-sending OO. It also means CLOS programs are usually more like Lisp programs with a class system attached than they are like Java programs translated into Lisp.
CLOS is part of the ANSI standard, so every conforming Common Lisp provides it. The MOP is not standard but is implemented in some form by all of them.
| Implementation | Status | CLOS / MOP notes |
|---|---|---|
| SBCL (Steel Bank Common Lisp) | Free software, actively developed | Full ANSI CLOS, full AMOP-style MOP |
| CCL (Clozure CL) | Free software, MIT/Apache mix | Full ANSI CLOS, full MOP |
| ECL (Embeddable Common Lisp) | Free software | Full CLOS, MOP with some gaps closed by closer-mop |
| ABCL (Armed Bear Common Lisp) | Free software, runs on JVM | Full CLOS, MOP largely supported |
| CLISP | Free software | Full CLOS, extensive MOP |
| GCL (GNU Common Lisp) | Free software | Full CLOS |
| LispWorks | Commercial, Lispworks Ltd. | Full CLOS, full MOP |
| Allegro Common Lisp | Commercial, Franz Inc. | Full CLOS, full MOP, AllegroCache extensions |
| Symbolics Genera | Historical, Symbolics Inc. | CLOS available alongside Flavors |
| Lucid Common Lisp | Historical, Lucid Inc. | One of the original CLOS reference implementations |
The closer-mop library by Pascal Costanza papers over differences in MOP support across implementations and is the usual way library code targets multiple Common Lisps when it needs metaprogramming.
CLOS was developed inside what was, in the late 1980s, the dominant infrastructure for AI research. Common Lisp was the working language at Symbolics, at Xerox PARC, at Lucid, and in many university AI labs. Several ideas that originated or were polished in CLOS later spread well outside the Lisp world.
functools.singledispatch, added in Python 3.4, is a deliberately small subset of CLOS-style generic functions.What keeps CLOS in the conversation is not nostalgia but the fact that the basic design choices, generic functions instead of message passing, multiple dispatch, method combinations, classes as first-class metaobjects, were correct enough that languages designed thirty years later still adopt them one at a time.
CLOS is not without rough edges. Its performance model is harder to reason about than single-dispatch OO: dispatch cost depends on the number of applicable methods and on the method combination, and adding a method anywhere can invalidate a dispatch cache. The lack of built-in encapsulation means programmers have to build conventions on top, and the conventions vary across teams. Method combinations and :around methods are powerful but can make it hard to read a generic function call in isolation; you may need to inspect every applicable method to know what actually runs. The MOP is a sharp tool: subclassing standard-class casually can break compiler optimizations, and AMOP itself spends considerable space on what can and cannot be customized without breaking other parts of the protocol.