Prototypal Inheritance: Why Classes Were a Mistake in a Classless Language
There’s a moment every JavaScript developer reaches where something doesn’t quite add up. You’ve learned that JavaScript has class syntax — it looks familiar, it feels like Java or Python — and then someone tells you: “it’s just syntactic sugar.” You nod. But what does that actually mean? And why does it matter?
To understand the controversy around ES6 classes, we need to go back to 1995, when Brendan Eich built JavaScript in roughly ten days. He didn’t give it classes. Not by accident — by design.
1. The Original Vision: Objects All the Way Down
JavaScript was inspired partly by Self, a prototype-based language from Xerox PARC. In Self, there are no class blueprints. Instead, objects inherit directly from other objects. You find something that’s close to what you want, clone it, and tweak it. That’s it.
JavaScript adopted this model. Every object has an internal link — its prototype — pointing to another object. When you look up a property that doesn’t exist on an object, JavaScript climbs this chain until it finds the property or hits null. This is the prototype chain, and it’s how JavaScript has always worked under the hood, with or without the class keyword.
“JavaScript has a prototype-based object system, not a class-based one. Classes in ES6 don’t change that. They’re a layer of paint over something fundamentally different.”— Douglas Crockford, JavaScript: The Good Parts
The power here is genuine flexibility. In a classical system, a class is a rigid definition. In a prototypal system, any object can serve as the “template” for another. You can mix, extend, and compose at runtime with a freedom that class hierarchies struggle to offer.
2. So Where Do Classes Come From?
From developer pressure — pure and simple. As JavaScript grew from a browser toy into a platform powering large applications, teams coming from Java, C#, or Python kept reaching for familiar patterns. They wanted class Animal, they wanted extends, they wanted the vocabulary they already knew.
The community built workarounds. Libraries like Backbone.js shipped with .extend() methods. CoffeeScript, which compiled to JavaScript, popularized class syntax years before ES6 shipped it natively. The demand was undeniable.
In 2015, ECMAScript 6 (ES2015) made it official. JavaScript got class, extends, super, and constructor. The community largely cheered. A vocal minority groaned.
Stack Overflow Survey: Most Used Web Technologies (JavaScript Dominance)
3. Why Prototypes Are Actually More Flexible
Here’s where it gets interesting. The prototype model isn’t a limitation that classes fix — it’s a different, and in many ways more powerful, paradigm.
Consider a few things prototypes give you that classical inheritance struggles with:
Object composition over inheritance hierarchies. In a class system, inheritance is a tree. Once you commit to Animal → Mammal → Dog, you’re locked in. Prototypes let you assemble behaviour from multiple source objects — something JavaScript developers now do routinely with object spread and mixins, patterns that feel natural in a prototypal world but are awkward in classical systems.
Runtime flexibility. You can change an object’s prototype after creation. You can add methods to Object.prototype (though you shouldn’t) and have them instantly available across every object in your program. The chain is live, not compiled.
No category errors. In classical inheritance, you must decide upfront whether something is a type or an instance. In prototype-based code, that distinction dissolves. Everything is just an object pointing at another object.
The influential engineer Eric Elliott argued strenuously that ES6 classes would push developers toward fragile inheritance hierarchies rather than the more resilient composition patterns JavaScript’s object model naturally encourages.
4. The Confusion Classes Create
Here’s the irony: class syntax was added to make JavaScript easier to understand for developers from other languages. In practice, it often creates a different kind of confusion — particularly because it hides how JavaScript actually works.
A newcomer writes a class, inherits from it, and everything seems fine. Then they run into this binding issues that wouldn’t exist in Java. Or they discover that instanceof behaves strangely across different execution contexts. Or they try to use a class like a factory and realize it won’t work without new. These aren’t bugs in their code — they’re the prototype system showing through the class-shaped costume.
// Run this in your browser console or Node.js
function Animal(name) {
this.name = name;
}
Animal.prototype.speak = function() {
return this.name + ' makes a sound.';
};
function Dog(name) {
Animal.call(this, name);
}
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;
Dog.prototype.speak = function() {
return this.name + ' barks.';
};
const d = new Dog('Rex');
console.log(d.speak()); // "Rex barks."
console.log(d instanceof Dog); // true
console.log(d instanceof Animal); // true
The code above is how you expressed inheritance before ES6 classes. It’s verbose and mechanical — nobody misses writing it. But it makes one thing brutally transparent: there are no classes here. There are functions, there are objects, and there are prototype links. ES6 classes wrap this in familiar syntax without changing what’s happening beneath.
Developer Sentiment: Class Syntax in JavaScript (Community Survey Data)

5. What the Debate Reveals About Language Design
The class controversy is really a proxy for a deeper question: should a language evolve to match its users’ habits, or should it push users toward better habits?
JavaScript, famously, has always tried to do both at once. It’s a language of pragmatic compromises. TC39, the committee that governs JavaScript’s evolution, has to balance backward compatibility, developer ergonomics, and language coherence — often in conflict.
Adding classes wasn’t just about syntax. It was a statement that JavaScript was a “serious” language suitable for large teams. Whether that framing helps or hurts is still argued. Some developers find class-based patterns lead to better-organized codebases. Others feel the class abstraction quietly pushes you into design patterns that JavaScript’s own idioms argue against.
| Aspect | Prototypal (Pre-ES6) | Class Syntax (ES6+) |
|---|---|---|
| Mental model | Objects delegating to objects | Blueprints instantiating instances |
| Inheritance style | Flat delegation chains | Hierarchical trees |
| Runtime flexibility | High — prototype can change at runtime | Low — class shape is fixed at definition |
| Verbosity | High (manual wiring) | Low (familiar syntax) |
| Beginner clarity | Low — concepts unfamiliar | High — looks like other OOP languages |
| Risk of misuse | Moderate — flexible but explicit | Higher — deep hierarchies, this surprises |
| Composition-friendly? | Yes — natural with mixins/Object.assign | Less so — encourages extension over composition |
5.1 The “Gorilla-Banana Problem” — Still Relevant
Joe Armstrong, creator of Erlang, once put it elegantly: “The problem with object-oriented languages is they’ve got all this implicit environment that they carry around with them. You wanted a banana but what you got was a gorilla holding the banana and the entire jungle.” Class hierarchies in any language — JavaScript included — invite this exact problem.
In JavaScript, prototypal patterns naturally encourage you to compose smaller objects. Class syntax doesn’t forbid that, but the mental gravity of extends pulls developers toward deep inheritance trees, which is precisely what Armstrong was describing. This is why experienced JavaScript developers tend to recommend understanding prototypes before reaching for class syntax.
6. Modern JavaScript: Can’t We Have Both?
In practice, most production JavaScript today uses class syntax — and that’s fine. The key is knowing what’s underneath. React’s function components, for example, largely moved away from class components in 2019 with the introduction of hooks. That shift was partly motivated by the cognitive overhead classes add around this and lifecycle binding.
Worth noting: The React team’s move to function components wasn’t a rejection of OOP broadly — it was a recognition that for UI composition, the class model created unnecessary friction. Their motivation document is worth reading if you want a concrete example of class-syntax pain points in a large ecosystem.
TypeScript, which adds static typing to JavaScript, makes classes genuinely more useful because the type system catches many of the runtime surprises that make JavaScript class hierarchies risky. It’s no accident that TypeScript users tend to be more positive about class syntax — the language compensates for some of the gaps.
Meanwhile, functional programming patterns — closures, higher-order functions, pure functions — have grown in popularity precisely because they sidestep the inheritance question entirely. Libraries like Ramda and the adoption of immutable patterns reflect a part of the JavaScript community that decided classes were the wrong answer to a question they weren’t asking.
7. What We’ve Learned
JavaScript was designed as a prototype-based language where objects inherit directly from other objects — a model rooted in the Self language and built for flexibility. When ES6 introduced class syntax in 2015, it wasn’t a change to how JavaScript works; it was a layer of familiar syntax over the same prototype system, driven by demand from developers coming from class-based languages like Java and Python.
The controversy isn’t really about syntax — it’s about mental models. Class syntax encourages inheritance hierarchies and hides the delegation-based nature of JavaScript’s objects, which can lead to confusion and fragile designs. Prototypal patterns, while more verbose before ES6, more naturally invite object composition and runtime flexibility. The debate reveals a fundamental tension in language design: whether a language should meet developers where they are, or guide them toward better idioms.
The takeaway is practical: use class syntax if it helps your team’s clarity, but understand what sits beneath it. Knowing that class is syntactic sugar over prototype chains isn’t a trivia point — it’s the knowledge that will make you a significantly more effective JavaScript developer.


