Interview Importance: π΄ Critical β Asked in 90% of JavaScript interviews. Understanding prototypes is fundamental to understanding how JavaScript works under the hood.
JavaScript uses prototype-based inheritance, meaning objects inherit properties and methods from other objects via the prototype chain β not through classical class-based inheritance like Java or C++.
Every JavaScript object has an internal link to another object called its prototype. When you access a property on an object, JavaScript first looks at the object itself. If not found, it looks up the prototype chain until it finds the property or reaches null.
+-----------------+
| Your Object |
| { name: "X" } |
+--------+--------+
| [[Prototype]]
βΌ
+-----------------+
| Parent Prototype|
| { greet() } |
+--------+--------+
| [[Prototype]]
βΌ
+-----------------+
| Object.prototype|
| { toString() } |
+--------+--------+
| [[Prototype]]
βΌ
null
Without prototypes, each object instance would have its own copy of every method:
// β Without prototypes - each instance has its own copy function PersonBad(name) { this.name = name; this.greet = function() { // New function created for EACH instance console.log(`Hello, ${this.name}`); }; } const p1 = new PersonBad("Alice"); const p2 = new PersonBad("Bob"); console.log(p1.greet === p2.greet); // false - Different function objects! // β With prototypes - all instances share the same method function PersonGood(name) { this.name = name; } PersonGood.prototype.greet = function() { console.log(`Hello, ${this.name}`); }; const p3 = new PersonGood("Alice"); const p4 = new PersonGood("Bob"); console.log(p3.greet === p4.greet); // true - Same function object!
prototype Property (Functions)Every JavaScript function has a prototype property (an object) that becomes the prototype of instances created with new.
function Person(name, age) { this.name = name; this.age = age; } // Adding method to the prototype Person.prototype.greet = function() { console.log(`Hello, I'm ${this.name} and I'm ${this.age} years old.`); }; // Creating instances const person1 = new Person("Alice", 25); const person2 = new Person("Bob", 30); person1.greet(); // Hello, I'm Alice and I'm 25 years old. person2.greet(); // Hello, I'm Bob and I'm 30 years old.
person1.greet() is called?Step 1: JavaScript looks for `greet` on person1 object
-> person1 = { name: "Alice", age: 25 }
-> `greet` NOT found on person1
Step 2: JavaScript looks up the prototype chain
-> person1.__proto__ === Person.prototype
-> Person.prototype = { greet: function() {...} }
-> `greet` FOUND!
Step 3: Execute greet() with `this` = person1
-> Output: "Hello, I'm Alice and I'm 25 years old."
__proto__ Property (Objects)Every object has a __proto__ property pointing to its prototype. This is how the chain is traversed.
console.log(person1.__proto__ === Person.prototype); // true console.log(Person.prototype.__proto__ === Object.prototype); // true console.log(Object.prototype.__proto__); // null (end of chain)
const person1 = new Person("Alice", 25); // The prototype chain: person1 +-- __proto__ -> Person.prototype +-- __proto__ -> Object.prototype +-- __proto__ -> null
function Person(name, age) { this.name = name; this.age = age; } Person.prototype.greet = function() { console.log(`Hello, I'm ${this.name}`); }; function Employee(name, age, job) { Person.call(this, name, age); // Step 1: Call parent constructor this.job = job; } // Step 2: Set up prototype chain Employee.prototype = Object.create(Person.prototype); // Step 3: Fix the constructor reference Employee.prototype.constructor = Employee; // Step 4: Add Employee-specific methods Employee.prototype.work = function() { console.log(`${this.name} is working as a ${this.job}`); }; // Usage const emp = new Employee("Charlie", 28, "Engineer"); emp.greet(); // "Hello, I'm Charlie" (inherited) emp.work(); // "Charlie is working as a Engineer" (own method)
emp (instance)
| { name: "Charlie", age: 28, job: "Engineer" }
|
+-- __proto__ -> Employee.prototype
| { constructor: Employee, work: fn }
|
+-- __proto__ -> Person.prototype
| { constructor: Person, greet: fn }
|
+-- __proto__ -> Object.prototype
| { toString, hasOwnProperty, ... }
|
+-- __proto__ -> null
| Step | Code | Purpose |
|---|---|---|
| 1 | Person.call(this, name, age) | Initialize parent properties on this |
| 2 | Employee.prototype = Object.create(Person.prototype) | Link prototype chain WITHOUT calling Person() |
| 3 | Employee.prototype.constructor = Employee | Fix constructor reference (otherwise points to Person) |
| 4 | Add methods to Employee.prototype | Define child-specific behavior |
new Instead of Object.create// β WRONG - Calls Person() with no arguments Employee.prototype = new Person(); // β CORRECT - Creates object with Person.prototype as prototype Employee.prototype = Object.create(Person.prototype);
ES6 class is syntactic sugar over prototype-based inheritance:
class Person { constructor(name, age) { this.name = name; this.age = age; } greet() { console.log(`Hello, I'm ${this.name}`); } } class Employee extends Person { constructor(name, age, job) { super(name, age); // Calls Person constructor this.job = job; } work() { console.log(`${this.name} is working as a ${this.job}`); } } const emp = new Employee("Daisy", 35, "Designer"); emp.greet(); // Inherited emp.work(); // Own method
// ES6 classes create the SAME prototype chain console.log(emp.__proto__ === Employee.prototype); // true console.log(Employee.prototype.__proto__ === Person.prototype); // true console.log(typeof Person); // "function" - Classes are functions!
| Method | Purpose | Example |
|---|---|---|
Object.create(proto) | Create object with specified prototype | Object.create(Person.prototype) |
Object.getPrototypeOf(obj) | Get an object's prototype | Object.getPrototypeOf(emp) |
Object.setPrototypeOf(obj, proto) | Set an object's prototype (slow!) | Object.setPrototypeOf(obj, newProto) |
obj.hasOwnProperty(prop) | Check if property exists on object (not prototype) | emp.hasOwnProperty('name') |
prop in obj | Check if property exists anywhere in chain | 'greet' in emp |
obj instanceof Constructor | Check if object is instance of constructor | emp instanceof Person |
const emp = new Employee("Test", 25, "Dev"); // hasOwnProperty vs in console.log(emp.hasOwnProperty('name')); // true (own property) console.log(emp.hasOwnProperty('greet')); // false (inherited) console.log('greet' in emp); // true (found in chain) // instanceof checks the prototype chain console.log(emp instanceof Employee); // true console.log(emp instanceof Person); // true console.log(emp instanceof Object); // true console.log(emp instanceof Array); // false
When you define a method on an object or its prototype that already exists higher in the chain:
class Animal { speak() { console.log("Animal speaks"); } } class Dog extends Animal { speak() { console.log("Dog barks"); } speakBoth() { super.speak(); // Call parent's speak this.speak(); // Call own speak } } const dog = new Dog(); dog.speak(); // "Dog barks" (overridden) dog.speakBoth(); // "Animal speaks" then "Dog barks"
dog.speak() is called
Step 1: Look for `speak` on dog instance
-> dog = {} (no own properties)
-> NOT found
Step 2: Look on Dog.prototype
-> Dog.prototype = { speak: fn, speakBoth: fn }
-> FOUND! Execute Dog.prototype.speak()
-> Output: "Dog barks"
Note: Animal.prototype.speak is never reached
because Dog.prototype.speak shadows it
__proto__ and prototype?Answer:
prototype is a property of functions β it becomes the prototype of instances created with new__proto__ is a property of all objects β it points to the object's actual prototypefunction Foo() {} const obj = new Foo(); console.log(Foo.prototype); // { constructor: Foo } console.log(obj.__proto__); // { constructor: Foo } console.log(obj.__proto__ === Foo.prototype); // true
Answer:
const obj = { name: "Test" }; Object.prototype.inherited = "I'm inherited"; console.log(obj.hasOwnProperty('name')); // true console.log(obj.hasOwnProperty('inherited')); // false console.log('inherited' in obj); // true
Answer: It affects ALL arrays in your application (prototype pollution). This is generally discouraged:
// β Dangerous - affects all arrays Array.prototype.first = function() { return this[0]; }; [1, 2, 3].first(); // 1 β works but risky // β Safer - create utility function const first = (arr) => arr[0];
instanceof and how it worksAnswer:
instanceof checks if an object's prototype chain contains Constructor.prototype:
function Person() {} const p = new Person(); // p instanceof Person checks: // p.__proto__ === Person.prototype? // If not, p.__proto__.__proto__ === Person.prototype? // Continue until null... console.log(p instanceof Person); // true console.log(p instanceof Object); // true (Object.prototype is in chain)
function A() {} function B() {} A.prototype = B.prototype = {}; const a = new A(); console.log(a instanceof A); // ? console.log(a instanceof B); // ?
Answer: Both are true because a.__proto__ points to the shared {} object, which is both A.prototype and B.prototype.
function Parent() {} function Child() {} Child.prototype = Object.create(Parent.prototype); // β Child.prototype.constructor is now Parent! console.log(new Child().constructor); // Parent // β Fix: Reset constructor Child.prototype.constructor = Child;
prototypeconst Foo = () => {}; console.log(Foo.prototype); // undefined // β Can't use as constructor new Foo(); // TypeError: Foo is not a constructor
function Foo() {} const obj = new Foo(); // This works - adding to existing prototype Foo.prototype.greet = function() { console.log("Hi"); }; obj.greet(); // "Hi" // This breaks existing instances! Foo.prototype = { newMethod: function() {} }; obj.greet(); // Still "Hi" - obj still linked to OLD prototype
| Concept | Description |
|---|---|
| Prototype | An object from which other objects inherit properties |
prototype | Property of functions; becomes __proto__ of instances |
__proto__ | Internal link to an object's prototype |
| Prototype Chain | The chain of prototypes JavaScript searches for properties |
Object.create() | Create object with specified prototype |
| ES6 Classes | Syntactic sugar over prototype inheritance |
Object.prototype.__proto__ which is null)class is syntactic sugar β same prototype mechanism underneathObject.create() for inheritance β not new Parent()Test your understanding with 3 quick questions