Prototype - The Backbone of JavaScript Objects
JavaScript's prototype system is one of its most powerful yet misunderstood features. Unlike classical object-oriented languages that use classes as blueprints, JavaScript uses prototypes to implement inheritance and share functionality between objects. Let's dive deep into this fundamental concept.
Understanding Prototypes in JavaScript
The Blueprint Analogy
Think of prototypes like architectural blueprints for houses. When you build a house, you don't recreate the blueprint each time—you reference the original design. Similarly, when you create objects in JavaScript, they don't duplicate all methods and properties. Instead, they reference a shared prototype object.
// When you create an array
const myArray = [1, 2, 3];
// It doesn't copy methods like push, pop, map
// Instead, it links to Array.prototype
console.log(myArray.__proto__ === Array.prototype); // true
// So when you call a method
myArray.push(4);
// JavaScript looks up the prototype chain to find 'push'
What Exactly is a Prototype?
Every JavaScript object has an internal property called [[Prototype]] (accessed via __proto__ or Object.getPrototypeOf()). This property is a reference to another object, forming a chain that JavaScript traverses when looking for properties or methods.
const person = {
greet() {
return `Hello, I'm ${this.name}`;
}
};
const john = Object.create(person);
john.name = 'John';
console.log(john.greet()); // "Hello, I'm John"
// john doesn't have greet(), but its prototype does!
Constructor Functions and the prototype Property
When you create a constructor function, JavaScript automatically gives it a prototype property. This property becomes the prototype of all instances created with that constructor.
function Car(brand, model) {
this.brand = brand;
this.model = model;
}
// Adding methods to the prototype
Car.prototype.getInfo = function() {
return `${this.brand} ${this.model}`;
};
const tesla = new Car('Tesla', 'Model 3');
const bmw = new Car('BMW', 'X5');
console.log(tesla.getInfo()); // "Tesla Model 3"
console.log(bmw.getInfo()); // "BMW X5"
// Both instances share the same method
console.log(tesla.getInfo === bmw.getInfo); // true
The Prototype Chain - How Inheritance Works
The prototype chain is JavaScript's mechanism for inheritance. When you try to access a property on an object, JavaScript follows this lookup process:
Check if the property exists on the object itself
If not, check the object's prototype
If not, check the prototype's prototype
Continue until reaching
Object.prototypeIf still not found, return
undefined
function Animal(name) {
this.name = name;
}
Animal.prototype.speak = function() {
return `${this.name} makes a sound`;
};
function Dog(name, breed) {
Animal.call(this, name); // Call parent constructor
this.breed = breed;
}
// Set up inheritance
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;
// Add Dog-specific method
Dog.prototype.bark = function() {
return `${this.name} barks!`;
};
const buddy = new Dog('Buddy', 'Golden Retriever');
console.log(buddy.name); // "Buddy" (own property)
console.log(buddy.bark()); // "Buddy barks!" (Dog.prototype)
console.log(buddy.speak()); // "Buddy makes a sound" (Animal.prototype)
console.log(buddy.toString()); // "[object Object]" (Object.prototype)
Visualizing the Prototype Chain

Classical vs Prototype-Based Inheritance
| Classical Inheritance | Prototype-Based Inheritance |
| Classes are blueprints | Objects inherit from objects |
| Instances are copies of class structure | Instances link to prototype objects |
| Inheritance through class hierarchy | Inheritance through prototype chain |
| Static structure defined at compile time | Dynamic structure can change at runtime |
| Used in Java, C++, C# | Used in JavaScript, Lua |
Comparing Prototype Access Methods
JavaScript provides several ways to work with prototypes. Let's compare them:
1. Object.create()
The modern, recommended way to create objects with a specific prototype.
const animal = {
type: 'Unknown',
describe() {
return `This is a ${this.type}`;
}
};
const cat = Object.create(animal);
cat.type = 'Cat';
console.log(cat.describe()); // "This is a Cat"
console.log(Object.getPrototypeOf(cat) === animal); // true
Pros: Clean, explicit, widely supported
Use case: When you want to create an object that inherits from another object
2. __proto__ Property
Direct access to an object's prototype (deprecated but still widely used).
const dog = { name: 'Rex' };
const puppy = { age: 1 };
puppy.__proto__ = dog;
console.log(puppy.name); // "Rex"
// Better alternative:
Object.setPrototypeOf(puppy, dog);
Pros: Simple, direct access
Cons: Deprecated, performance issues
Use case: Debugging or legacy code only
3. prototype Property
Used with constructor functions to define shared methods.
function Vehicle(type) {
this.type = type;
}
Vehicle.prototype.move = function() {
return `${this.type} is moving`;
};
const car = new Vehicle('Car');
console.log(car.move()); // "Car is moving"
Pros: Memory efficient, traditional pattern
Use case: Constructor functions, before ES6 classes
Modern Approach with ES6 Classes
ES6 classes are syntactic sugar over prototypes:
class Shape {
constructor(color) {
this.color = color;
}
describe() {
return `A ${this.color} shape`;
}
}
class Circle extends Shape {
constructor(color, radius) {
super(color);
this.radius = radius;
}
getArea() {
return Math.PI * this.radius ** 2;
}
}
const redCircle = new Circle('red', 5);
console.log(redCircle.describe()); // "A red shape"
console.log(redCircle.getArea().toFixed(2)); // "78.54"
// Under the hood, it's still prototypes!
console.log(redCircle.__proto__ === Circle.prototype); // true
Modifying Prototypes - Do's and Don'ts
✅ Do's
1. Add methods to your own constructor prototypes
function User(name) {
this.name = name;
}
User.prototype.sayHello = function() {
return `Hello, I'm ${this.name}`;
};
2. Use Object.create() for delegation
const methods = {
log() { console.log(this.value); }
};
const obj = Object.create(methods);
obj.value = 42;
3. Check property ownership
const person = { name: 'Alice' };
person.__proto__.age = 30;
console.log(person.hasOwnProperty('name')); // true
console.log(person.hasOwnProperty('age')); // false
❌ Don'ts
1. Never modify built-in prototypes (except for polyfills)
// BAD - Don't do this!
Array.prototype.myMethod = function() {
// This affects ALL arrays everywhere
};
// If you must (for polyfills), check first:
if (!Array.prototype.includes) {
Array.prototype.includes = function(searchElement) {
// Polyfill implementation
};
}
2. Avoid setting proto in production
// BAD - Performance killer
obj.__proto__ = somePrototype;
// GOOD - Use these instead
Object.setPrototypeOf(obj, somePrototype);
// or better yet:
const obj = Object.create(somePrototype);
3. Don't create very long prototype chains
// BAD - Too many levels
A.prototype = Object.create(B.prototype);
B.prototype = Object.create(C.prototype);
C.prototype = Object.create(D.prototype);
D.prototype = Object.create(E.prototype);
// GOOD - Keep it shallow, use composition instead
Performance Considerations
// Slow: Property lookup travels up the chain
const deep = Object.create(
Object.create(
Object.create(
Object.create({ value: 42 })
)
)
);
console.log(deep.value); // Has to traverse 4 levels
// Fast: Direct property access
const shallow = { value: 42 };
console.log(shallow.value); // Immediate access
Practical Examples
Example 1: Creating a Simple Plugin System
const Plugin = {
init() {
console.log(`${this.name} initialized`);
}
};
function createPlugin(name, functionality) {
const plugin = Object.create(Plugin);
plugin.name = name;
plugin.execute = functionality;
return plugin;
}
const logger = createPlugin('Logger', function(msg) {
console.log(`[LOG]: ${msg}`);
});
logger.init(); // "Logger initialized"
logger.execute('Hello World'); // "[LOG]: Hello World"
Example 2: Implementing Method Sharing
const mathOperations = {
add(a, b) { return a + b; },
subtract(a, b) { return a - b; }
};
function Calculator(name) {
this.name = name;
this.history = [];
}
Calculator.prototype = Object.create(mathOperations);
Calculator.prototype.calculate = function(operation, a, b) {
const result = this[operation](a, b);
this.history.push(`${operation}(${a}, ${b}) = ${result}`);
return result;
};
const calc = new Calculator('MyCalc');
console.log(calc.calculate('add', 5, 3)); // 8
console.log(calc.history); // ["add(5, 3) = 8"]
Conclusion
Prototypes are the foundation of JavaScript's object model. Understanding them helps you:
Write more memory-efficient code by sharing methods
Understand how inheritance works in JavaScript
Debug complex object hierarchies
Make informed decisions about using classes vs prototypes
Avoid common pitfalls when extending objects
While ES6 classes provide a more familiar syntax, prototypes remain the underlying mechanism. Mastering prototypes gives you deeper insight into how JavaScript truly works under the hood.
Key Takeaways:
Every object has a prototype (except Object.prototype)
Prototypes enable inheritance through delegation
Use Object.create() for modern prototype manipulation
Avoid modifying built-in prototypes
ES6 classes are syntactic sugar over prototypes
Keep prototype chains shallow for better performance
Happy coding! 🚀