Prototype-Based Programming: Objects Without Classes

OOP Fundamentals treats "class" and "object" as two different kinds of thing — a class is a blueprint, an object is a concrete instance built from it, and that distinction runs underneath every example on this site's OOP pages. It isn't a universal truth about object orientation, though; it's one specific design choice, made by Simula and inherited by almost every language that followed it. Prototype-based programming makes a different choice: there is no separate class construct at all, and every object is built directly from another object.

The Core Idea: Objects Building Objects

In a class-based language, to get a new kind of thing you write a class, then instantiate it. In a prototype-based language, to get a new kind of thing you take an existing object — the prototype — and either clone it or create a new object that delegates to it for whatever it doesn't define itself. There's no template that exists independently of any object; every object either is a prototype for something else or was itself built from one. The class/instance distinction that OOP Fundamentals treats as fundamental simply isn't present in the model at all — it collapses into one kind of thing: objects, related to each other by cloning or delegation instead of by class membership.

Self, built at Xerox PARC in the mid-1980s, is the language that worked this out most thoroughly. Its own account of the design describes prototypes as combining what class-based languages keep separate — inheritance and instantiation — into a single, simpler mechanism, and unifies variables and methods into one construct it calls a slot1. An object in Self is just a bag of slots; some slots hold data, some hold code, and a message sent to the object is looked up by searching its own slots first and then, if not found there, the slots of whatever it inherits from — its prototype.

Delegation, Not Classification

Lieberman's foundational paper on the topic names the underlying mechanism precisely: where class-based inheritance asks "what class does this object belong to?", prototype-based systems use delegation — an object that doesn't have a particular piece of behaviour simply forwards the message to another object (its prototype) that does2. Lieberman argues this is strictly more flexible than class-based inheritance, for a specific reason: any object can serve as a prototype for another, at any time, and what a given object delegates to can change while the program is running. A class hierarchy is fixed at compile time (or close to it); a delegation chain between prototype objects is just object references, and object references can be reassigned.

A later attempt by several of the field's original researchers to reconcile the two camps — half-seriously titled "the Treaty of Orlando" — is worth knowing about precisely because it refuses to declare a winner: it identifies two genuinely independent mechanisms, cloning (a new object built in an existing object's image, then free to diverge) and what it calls sharing "by reference" or delegation (an object that stays linked to another and consults it for anything not defined locally), and shows that a given prototype system can pick either, both, or something in between3. Self supports both. So, in practice, does JavaScript.

Prototypes in Practice: JavaScript

Self stayed a research language; the prototype model reached millions of working programmers through JavaScript. Every JavaScript object carries an internal link — formally, the [[Prototype]] internal slot — to another object it delegates to for any property it doesn't have itself, and property lookup walks this prototype chain upward until it finds a match or runs out of chain4. Object.create(proto) makes this mechanism directly visible: it creates a new, empty object whose prototype is exactly the object passed in, with no class involved anywhere.

This produces real, practical differences from the class-based examples elsewhere on this site, not just a different vocabulary for the same thing:

  • An object's shape isn't fixed by a class. Any individual JavaScript object can gain or lose properties at runtime, independent of every other object built from the same prototype — there's no class definition constraining what instances are allowed to look like, because there's no class.
  • Prototypes are ordinary objects, not a special kind of thing. A function used as a constructor's prototype can itself be modified, inspected, or replaced at runtime; in a class-based language, altering the class itself while the program runs is unusual and often unsupported. In JavaScript, it's just mutating an object like any other.
  • Inheritance is late-bound, all the way up. Because lookup walks the actual live chain of prototype objects at the moment a property is accessed, changing an object's prototype after other objects were already built from it can change what those existing objects appear to inherit — something a compiled class hierarchy simply cannot do.

Modern JavaScript's class keyword doesn't change any of this — it's syntax sitting on top of the same prototype mechanism, there specifically to make the model feel more familiar to programmers coming from class-based languages4. Writing class Dog extends Animal in JavaScript still produces ordinary objects linked by prototype delegation underneath; it's the same mechanism as this page describes, wearing more familiar clothing.

Why the Distinction Actually Matters

This isn't a purely academic difference. It has two concrete consequences worth carrying back into how the rest of this site's OOP material is read:

  • A JavaScript programmer who assumes classical inheritance will be surprised. Modifying Animal.prototype after some Dog objects already exist changes what those existing objects inherit — there is no equivalent surprise waiting in Java or C#, where a class's shape is fixed once compiled. Debugging that surprise is much faster once you know it's supposed to happen.
  • "Object-oriented" doesn't mean "class-based." The four pillars covered in OOP Fundamentals — encapsulation, inheritance, polymorphism, abstraction — are all present in Self and in prototype-based JavaScript. Objects still bundle state and behaviour, still delegate/inherit shared behaviour, still respond polymorphically to the same message in different ways. What's absent is only the specific mechanism of a separate class construct — which turns out not to be essential to any of the four pillars at all, just to the particular, dominant way of implementing them.

A Related but Different Move: PatLang's Classless Composition

This site's own PatLang project offers a genuinely useful third data point, precisely because it is not a prototype-based language and shouldn't be described as one — but it makes a structurally similar move for a different reason, which makes the comparison worth being precise about rather than blurring together. PatLang supports ordinary class-based single inheritance, but its class keyword is, in its own documentation's words, "deliberately optional sugar over the same object store" that its bare primitives (new, get, send) use directly5 — an object can be built and used with no class declared for it anywhere, by composing it directly from other objects, the same way this page's Object.create example builds a JavaScript object with no class involved.

The mechanism is different, though, and the difference is exactly the composition/inheritance distinction Inheritance & Composition covers via the GoF's "favour object composition over class inheritance" advice. A prototype chain is fundamentally an is-a-like relationship: an object stands in for another, delegating to it, in a chain that plays the same behavioural role a superclass does. PatLang's classless objects are built through genuine has-a composition instead — a Car object holding an Engine object as a field, with no shared-behaviour relationship implied at all, which is precisely the "plain composition" style PatLang's own documentation contrasts directly against its class-inheritance and trait (mixin) styles in the same worked example5. Two languages independently arrived at "let the programmer build useful objects without requiring a class declaration first" — JavaScript by making every object delegate through a prototype chain, PatLang by making class, traits, and plain has-a composition three independent, freely combinable mechanisms over one shared object store. Neither needed the other's specific solution to solve a version of the same underlying problem: not every object needs a class before it's useful.

See Also

For the class-based model this page has been contrasted against throughout, see OOP Fundamentals and Inheritance & Composition. For how a message being resolved differently depending on runtime type generalises beyond class hierarchies, see Polymorphism & Interfaces. For PatLang's own three independent, freely combinable sharing mechanisms — inheritance, traits, and plain composition — worked through with real, verified output, see PatLang Object Composition Styles.

References


  1. Ungar, D., & Smith, R. B. (1987). Self: The power of simplicity. OOPSLA '87 Conference Proceedings, published as ACM SIGPLAN Notices, 22(12), 227–241. https://doi.org/10.1145/38807.38828

  2. Lieberman, H. (1986). Using prototypical objects to implement shared behavior in object-oriented systems. OOPSLA '86 Conference Proceedings, 214–223. https://doi.org/10.1145/28697.28718

  3. Stein, L. A., Lieberman, H., & Ungar, D. (1988). A shared view of sharing: The Treaty of Orlando. In W. Kim & F. Lochovsky (Eds.), Object-Oriented Concepts, Applications, and Databases. Addison-Wesley.

  4. Ecma International. ECMAScript® Language Specification (ECMA-262). https://262.ecma-international.org/ — see the "Ordinary Object Internal Methods and Internal Slots" section for the [[Prototype]] slot and prototype-chain property lookup, and the "Class Definitions" section for class syntax's relationship to the underlying prototype mechanism.

  5. PatLang Object Composition Styles: Inheritance, Traits, and Plain Composition. https://parslow.net/topics/patlang/patlang-oo-styles.html