A practical guide inspired by “Learning Patterns” by Lydia Hallie & Addy Osmani
Patterns.dev describes the Prototype Pattern as a way to “share properties among many objects of the same type.” Instead of duplicating methods on every instance, JavaScript lets objects inherit from a shared prototype so they can find behavior up the chain.
1. Why prototypes?
When you need hundreds or thousands of similar objects, copying the same functions into each one wastes memory. By placing shared behavior on the prototype, every instance has one reference to that function instead of its own copy.
“The prototype pattern is a useful way to share properties among many objects of the same type… avoiding duplication of methods and properties, thus reducing the amount of memory used.” — Learning Patterns
2. A first example: Dogs that bark
class Dog {
constructor(name) {
this.name = name; // unique per instance
}
bark() { // lives on Dog.prototype
return `${this.name} says woof!`;
}
}
const fido = new Dog("Fido");
console.log(fido.bark()); // "Fido says woof!"
console.log(fido.__proto__ === Dog.prototype); // true
All Dog
instances share one bark
method on Dog.prototype
. Whenever JavaScript can’t find bark
directly on fido
, it looks up the prototype chain until it does.
3. Adding behavior after the fact
Dog.prototype.play = function () {
return `${this.name} is playing!`;
};
console.log(fido.play()); // "Fido is playing!"
Because every dog points to the same prototype, they all “magically” learn to play()
at runtime—without recreating or mutating individual instances.
4. Extending the chain: SuperDog
class SuperDog extends Dog {
fly() {
return `${this.name} takes off! ✈️`;
}
}
const daisy = new SuperDog("Daisy");
console.log(daisy.bark()); // inherited
console.log(daisy.fly()); // new ability
The prototype chain now looks like:
daisy → SuperDog.prototype → Dog.prototype → Object.prototype → null
JavaScript walks the chain until it finds each requested property.
5. Object.create
for direct delegation
Sometimes a full class
is overkill—you just want one object to delegate to another.
const dog = { bark() { return "woof"; } };
const pet1 = Object.create(dog); // prototype = dog
console.log(pet1.bark()); // "woof"
Object.create(proto)
sets proto as the new object’s prototype, letting you build ad‑hoc delegations on the fly.
6. When to reach for the Prototype Pattern
Use‑case | Why prototypes help |
---|---|
Many identical objects | Reuse the same functions, save memory |
Post‑creation patches | Add methods once, all instances get them |
Classical inheritance | Extend prototypes to build chains of behavior |
Flyweight objects | Share heavy data across millions of instances |
Tip: Don’t mutate built‑in prototypes (Array.prototype
, String.prototype
) in production code—it leads to “prototype pollution” and hard‑to‑track bugs.
7. Trade‑offs
✅ Pros
- Memory‑efficient sharing of behavior
- Dynamic: you can augment prototypes after instances exist
- Mimics “classical” inheritance, easing mental models
⚠️ Cons
- Overusing inheritance can create deep, fragile chains
- Prototype pollution can leak globals if not careful
- Modern ES6+ modules and composition often replace inheritance patterns
Conclusion
The Prototype Pattern is baked into JavaScript’s DNA. By understanding how objects delegate along the prototype chain, you can write code that’s both memory‑efficient and flexible—without reinventing classes or copying functions everywhere.
Next in the series we’ll explore Flyweight Pattern for crushing memory use in huge data sets—stay tuned for #LearningPatterns21!
This post is part of my “Learning Patterns” series. Follow the hash *#LearningPatterns** to catch every installment.*
Top comments (0)