Prototypes & Inheritance in JavaScript

Every object in JavaScript has a hidden link to another object. That hidden link points to its prototype - a behind-the-scenes connection that JavaScript uses automatically.

When you try to use a property or method on an object, JavaScript first looks at the object itself. If it doesn't find it there, it follows that hidden link and checks the prototype. If it still can't find it, it goes up another level. This is called the prototype chain.

Here's a simple real-world way to think about it. Imagine you don't know the answer to a question. You ask your parent. They don't know either, so they ask their parent. That chain of asking - up through generations - is exactly how the prototype chain works in JavaScript.

You can see the prototype of an object using Object.getPrototypeOf(), or the (unofficial but widely supported) __proto__ property.

javascript

const animal = {
  speak() {
    console.log("Some generic sound...");
  }
};

const dog = Object.create(animal); // dog's prototype is animal
dog.name = "Rex";

dog.speak(); // "Some generic sound..." - found on the prototype
console.log(dog.name); // "Rex" - found on dog itself

// Check the prototype link
console.log(Object.getPrototypeOf(dog) === animal); // true
console.log(dog.__proto__ === animal); // true (same thing, older syntax)

In the example above, speak() is not on the dog object directly. But because dog's prototype is animal, JavaScript finds it there. That's the prototype chain in action.

The prototype chain is a series of linked objects. It goes: your object → its prototype → that prototype's prototype → and so on, all the way up to null. When JavaScript hits null, it stops looking and returns undefined.

A great analogy is a family tree. You might inherit your eye colour from your parent, who inherited their height from their grandparent. Each generation passes traits down. In JavaScript, each object passes methods and properties down through the chain.

This is why things like .toString() and .hasOwnProperty() work on almost every object in JavaScript - they live way up at the top of the chain, on Object.prototype.

javascript

const obj = { title: "Dashboard" };

// hasOwnProperty lives on Object.prototype, not on obj directly
console.log(obj.hasOwnProperty("title")); // true
console.log(obj.hasOwnProperty("icon"));  // false

// toString also lives on Object.prototype
console.log(obj.toString()); // "[object Object]"

// Let's trace the chain manually
console.log(obj.__proto__ === Object.prototype); // true
console.log(Object.prototype.__proto__);          // null - end of chain

// A deeper chain example
function Vehicle() {}
function Car() {}
Car.prototype = Object.create(Vehicle.prototype);

const myCar = new Car();

console.log(myCar instanceof Car);     // true
console.log(myCar instanceof Vehicle); // true - it's in the chain!
console.log(Object.getPrototypeOf(Object.getPrototypeOf(myCar)) === Vehicle.prototype); // true

Every object you create in JavaScript ultimately has Object.prototype somewhere at the top of its chain. That is where all those built-in helpers come from.

Object.create() is one of the cleanest ways to set up inheritance in JavaScript. It lets you create a brand new object and explicitly set its prototype in one step.

Imagine a new employee joins a company. Instead of handing them every single company rule personally, you just give them a link to the company handbook. Object.create() is that handshake - the new object inherits everything from the handbook (its prototype) automatically.

You can also pass a second argument to Object.create() with property descriptors to define properties directly on the new object at the same time.

javascript

const componentProto = {
  render() {
    console.log(`Rendering ${this.name}`);
  },
  describe() {
    console.log(`Version ${this.version}`);
  }
};

// Create a new object with componentProto as its prototype
const app = Object.create(componentProto);
app.name = "Dashboard";
app.version = "2.1";

app.render();   // "Rendering Dashboard"
app.describe(); // "Version 2.1"

// app itself only has name and version
console.log(Object.keys(app)); // ["name", "version"]

// But it can still use render and describe via the prototype
console.log(Object.getPrototypeOf(app) === componentProto); // true

// Using the second argument to set own properties at creation time
const settingsPage = Object.create(componentProto, {
  name:    { value: "Settings", writable: true, enumerable: true },
  version: { value: "1.0", writable: true, enumerable: true }
});
settingsPage.render(); // "Rendering Settings"

Object.create(null) creates an object with no prototype at all - useful when you want a truly clean dictionary-style object with zero inherited behaviour.

ES6 introduced the class keyword, and it was a big deal for readability. But here's the important thing to understand: classes in JavaScript do not change how the language works. Under the hood, it is still prototypes all the way down.

A class is like a nice dashboard on your car. The dashboard makes things easier to use - buttons, dials, a clean layout. But behind the dashboard, the engine still works exactly the same way. JavaScript classes are syntactic sugar on top of prototype-based inheritance.

When you define a method inside a class, JavaScript puts it on the class's prototype automatically. You can verify this yourself - and it's a great thing to know for interviews.

javascript

// ---- Using a class (ES6 syntax) ----
class Animal {
  constructor(name) {
    this.name = name;
  }
  speak() {
    console.log(`${this.name} makes a noise.`);
  }
}

const cat = new Animal("Whiskers");
cat.speak(); // "Whiskers makes a noise."

// ---- Doing the exact same thing with prototypes directly ----
function AnimalProto(name) {
  this.name = name;
}
AnimalProto.prototype.speak = function () {
  console.log(`${this.name} makes a noise.`);
};

const cat2 = new AnimalProto("Whiskers");
cat2.speak(); // "Whiskers makes a noise."

// ---- Proof that class methods live on the prototype ----
console.log(typeof Animal);                     // "function" - classes are functions!
console.log(cat.speak === Animal.prototype.speak); // true

Both approaches produce the same result. The class syntax is much easier to read and write, especially as your code grows. But knowing the prototype mechanics behind it will make you a stronger developer - and will definitely come up in interviews.

Have you ever wondered why every array can call .push(), or every string can call .toUpperCase()? It is because arrays and strings each have a built-in prototype packed full of shared methods.

It's like all dogs sharing the same species blueprint. Every dog, no matter who owns it, can bark. The blueprint is Dog.prototype (so to speak). In JavaScript, every array shares Array.prototype and every string shares String.prototype. That's where all those helpful methods live.

You can even add your own methods to these built-in prototypes - though it is generally not recommended in production code, as it can break other code that shares the same prototype. It is fine in small experiments or learning exercises though.

javascript

const nums = [1, 2, 3];

// .push() lives on Array.prototype - not on nums directly
console.log(nums.hasOwnProperty("push")); // false
console.log("push" in nums);              // true (found on prototype)
console.log(Array.prototype.hasOwnProperty("push")); // true

// Same idea with strings
const greeting = "hello";
console.log(String.prototype.hasOwnProperty("toUpperCase")); // true
console.log(greeting.toUpperCase()); // "HELLO"

// The chain for an array goes:
// nums ? Array.prototype ? Object.prototype ? null
console.log(Object.getPrototypeOf(nums) === Array.prototype);           // true
console.log(Object.getPrototypeOf(Array.prototype) === Object.prototype); // true

// Adding a custom method to Array.prototype (for learning only!)
Array.prototype.sum = function () {
  return this.reduce((acc, val) => acc + val, 0);
};
console.log([1, 2, 3, 4].sum()); // 10

This is also why typeof [] returns "object" - arrays are objects, just with Array.prototype in their chain giving them all those extra powers.

This is one of the most confusing distinctions in JavaScript, and it trips up even experienced developers. Both relate to the prototype system, but they live in very different places.

__proto__ is a property that exists on every object instance. It is the actual hidden link that points to the object's prototype. When JavaScript walks the prototype chain, it follows these __proto__ links.

.prototype is a property on constructor functions (and classes). It is the object that will become the __proto__ of every instance created with new. It is not the function's own prototype - it is the blueprint for the instances it creates.

  • instance.__proto__ - the prototype the instance actually inherited from
  • Constructor.prototype - the blueprint that will become __proto__ for new instances

javascript

function User(name) {
  this.name = name;
}

User.prototype.greet = function () {
  console.log("Hi, I am " + this.name);
};

const alice = new User("Alice");

// alice.__proto__ IS User.prototype ΓÇö same object in memory
console.log(alice.__proto__ === User.prototype); // true

// User.prototype is NOT User's own __proto__
// It is the template for instances, not for the function itself
console.log(User.__proto__ === Function.prototype); // true

alice.greet(); // "Hi, I am Alice" ΓÇö found via __proto__ chain

javascript

// Prefer Object.getPrototypeOf() over __proto__ in production code
// __proto__ is non-standard (though universally supported) and may be deprecated

function Car(model) {
  this.model = model;
}

const myCar = new Car("Tesla");

// Both return the same result:
console.log(myCar.__proto__ === Car.prototype);              // true (old way)
console.log(Object.getPrototypeOf(myCar) === Car.prototype); // true (modern way)

// Summary of the full chain:
// myCar.__proto__          === Car.prototype
// Car.prototype.__proto__  === Object.prototype
// Object.prototype.__proto__ === null  (end of chain)

Before ES6 classes existed, the standard way to create many objects of the same "type" was a constructor function called with new.

When you call a function with new, JavaScript automatically does four things:

  1. Creates a brand new empty object.
  2. Sets the new object's __proto__ to Constructor.prototype.
  3. Runs the constructor with this bound to the new object.
  4. Returns the new object (unless the function explicitly returns a different object).

javascript

function Person(name, age) {
  // 'this' is the brand new object created by 'new'
  this.name = name;
  this.age  = age;
}

// Methods go on the prototype ΓÇö shared by all instances (saves memory)
Person.prototype.introduce = function () {
  console.log(`I am ${this.name}, ${this.age} years old.`);
};

const bob   = new Person("Bob", 28);
const carol = new Person("Carol", 34);

bob.introduce();   // "I am Bob, 28 years old."
carol.introduce(); // "I am Carol, 34 years old."

// Both instances share the same method via the prototype
console.log(bob.introduce === carol.introduce); // true

// Own properties are separate per instance
console.log(bob.name);   // "Bob"
console.log(carol.name); // "Carol"

javascript

// What 'new' does under the hood ΓÇö a manual simulation

function Person(name) {
  this.name = name;
}
Person.prototype.greet = function () {
  console.log("Hello, " + this.name);
};

// Calling: const p = new Person("Alice")
// ... is roughly equivalent to:
function manualNew(Constructor, ...args) {
  const obj = Object.create(Constructor.prototype); // steps 1 & 2
  Constructor.apply(obj, args);                     // step 3
  return obj;                                       // step 4
}

const p = manualNew(Person, "Alice");
p.greet();                                            // "Hello, Alice"
console.log(Object.getPrototypeOf(p) === Person.prototype); // true

ES6 extends makes prototype-based inheritance much easier to read and write. When class Dog extends Animal, JavaScript automatically wires up the prototype chain so instances of Dog can reach methods from both Dog.prototype and Animal.prototype.

super() inside the constructor calls the parent class constructor. You must call super() before using this in a derived class - failing to do so throws a ReferenceError.

javascript

class Animal {
  constructor(name) {
    this.name = name;
  }
  speak() {
    console.log(`${this.name} makes a sound.`);
  }
}

class Dog extends Animal {
  constructor(name, breed) {
    super(name); // must call super() before using 'this'
    this.breed = breed;
  }
  speak() {
    // Override the parent method
    console.log(`${this.name} barks.`);
  }
  info() {
    console.log(`${this.name} is a ${this.breed}.`);
  }
}

const rex = new Dog("Rex", "Labrador");
rex.speak(); // "Rex barks."  (Dog's own override)
rex.info();  // "Rex is a Labrador."

// The prototype chain is intact
console.log(rex instanceof Dog);    // true
console.log(rex instanceof Animal); // true

// extends wires: Dog.prototype.__proto__ === Animal.prototype
console.log(Object.getPrototypeOf(Dog.prototype) === Animal.prototype); // true

javascript

// Calling the parent method alongside the child's override using super.method()

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;
  }
  describe() {
    // Call parent describe() and then extend it
    return super.describe() + ` (circle, radius ${this.radius})`;
  }
  area() {
    return Math.PI * this.radius ** 2;
  }
}

const c = new Circle("red", 5);
console.log(c.describe());        // "A red shape (circle, radius 5)"
console.log(c.area().toFixed(2)); // "78.54"

Prototype methods use this to reference the calling object. But this is determined at call time, not definition time. The classic pitfall: detaching a method from its object loses the this binding.

javascript

function Timer(label) {
  this.label = label;
  this.count = 0;
}

Timer.prototype.tick = function () {
  this.count++;
  console.log(this.label + ": " + this.count);
};

const t = new Timer("Countdown");
t.tick(); // "Countdown: 1"  Γ£ô

// Problem: extracting the method loses 'this'
const detached = t.tick;
// detached(); // TypeError or NaN ΓÇö 'this' is no longer 't'

// Fix 1: bind() ΓÇö permanently ties 'this' to 't'
const boundTick = t.tick.bind(t);
boundTick(); // "Countdown: 2"  Γ£ô

// Fix 2: always call through the object
t.tick();    // "Countdown: 3"  Γ£ô

javascript

// Arrow functions in class constructors capture 'this' lexically
// This is a common pattern to avoid binding issues with callbacks

class Button {
  constructor(label) {
    this.label   = label;
    this.clicked = 0;
    // Arrow function: 'this' is always the Button instance
    this.handleClick = () => {
      this.clicked++;
      console.log(this.label + " clicked " + this.clicked + " time(s)");
    };
  }
}

const btn = new Button("Submit");

// Simulate passing the handler to an event listener
const handler = btn.handleClick;
handler(); // "Submit clicked 1 time(s)"  Γ£ô  ΓÇö arrow kept 'this'
handler(); // "Submit clicked 2 time(s)"  Γ£ô

Both check whether a property exists on an object, but they differ in where they look.

  • obj.hasOwnProperty('key') - returns true only if the property is directly on the object itself (not inherited).
  • 'key' in obj - returns true if the property exists anywhere in the prototype chain.

javascript

function Vehicle(make) {
  this.make = make;
}
Vehicle.prototype.drive = function () {
  console.log("Driving...");
};

const v = new Vehicle("Toyota");

// 'make' is an own property (set by the constructor)
console.log(v.hasOwnProperty("make"));  // true
console.log("make" in v);               // true

// 'drive' is inherited ΓÇö NOT an own property
console.log(v.hasOwnProperty("drive")); // false
console.log("drive" in v);              // true ← found on the prototype

// 'toString' lives on Object.prototype, far up the chain
console.log(v.hasOwnProperty("toString")); // false
console.log("toString" in v);              // true

// Practical pattern: iterate own properties only
for (let key in v) {
  if (v.hasOwnProperty(key)) {
    console.log("Own:", key); // Only logs "make"
  }
}

The modern alternative is Object.hasOwn(obj, key) (ES2022). It avoids edge cases with objects created via Object.create(null) which have no hasOwnProperty method at all.

javascript

// Object.hasOwn() ΓÇö ES2022 modern replacement for hasOwnProperty()

const settings = { theme: "dark", lang: "en" };
console.log(Object.hasOwn(settings, "theme"));   // true
console.log(Object.hasOwn(settings, "toString")); // false

// Edge case: an object with NO prototype can't call .hasOwnProperty()
const bare = Object.create(null);
bare.key = "value";

// bare.hasOwnProperty("key") → TypeError: bare.hasOwnProperty is not a function
// Object.hasOwn() works safely on any object:
console.log(Object.hasOwn(bare, "key")); // true

Here are the key things to take away from this tutorial:

  • Every object in JavaScript has a hidden link to a prototype object.
  • When JavaScript looks for a property, it walks up the prototype chain until it either finds it or reaches null.
  • Object.create(proto) lets you create a new object with a specific prototype, giving full control over inheritance.
  • ES6 class syntax is just a cleaner way to write prototype-based code - it does not change how JavaScript actually works under the hood.
  • Built-in types like Array, String, and Object all have their own prototypes (Array.prototype, String.prototype, Object.prototype) where shared methods live.
  • All prototype chains in JavaScript eventually end at Object.prototype, and then null.
  • instance.__proto__ is the actual prototype link on an instance. Constructor.prototype is the object that becomes __proto__ for new instances. They are not the same thing.
  • Prefer Object.getPrototypeOf() over accessing __proto__ directly in production code.
  • When you call a function with new, JavaScript creates a new object, sets its __proto__ to Constructor.prototype, runs the constructor with this bound to the new object, and returns it.
  • extends wires up the class prototype chain automatically. Always call super() in the subclass constructor before using this.
  • this inside a prototype method is determined at call time. Detaching a method from its object loses the binding - fix with .bind() or use an arrow function.
  • hasOwnProperty() checks only own properties; the in operator checks the entire prototype chain. Use Object.hasOwn() for a modern, safer alternative.
  • Avoid modifying built-in prototypes in production code - it can cause unexpected behaviour across your entire codebase.
  • What is a prototype in JavaScript? - A prototype is an object that other objects inherit properties and methods from. Every JavaScript object has an internal link ([[Prototype]]) pointing to its prototype.
  • What is the prototype chain? - The prototype chain is the series of linked objects JavaScript traverses when looking up a property. It ends when it reaches null.
  • What is the difference between __proto__ and prototype? - __proto__ is the actual link on an instance pointing to its prototype. prototype is a property on constructor functions (and classes) that becomes the __proto__ of instances created with new.
  • What does Object.create() do? - It creates a new object and sets the given argument as its prototype. Object.create(null) creates an object with no prototype at all.
  • Are JavaScript classes just syntactic sugar? - Yes. The class keyword introduced in ES6 makes prototype-based inheritance easier to write, but it doesn't change the underlying prototype mechanism. typeof MyClass returns "function".
  • What is the difference between __proto__ and Constructor.prototype? - __proto__ is the actual prototype link on an instance. Constructor.prototype is the object that will become __proto__ on instances created with new. After const x = new Foo(), x.__proto__ === Foo.prototype is true.
  • What does the new keyword do step by step? - It (1) creates a new empty object, (2) sets its __proto__ to Constructor.prototype, (3) runs the constructor with this bound to the new object, and (4) returns the new object.
  • What must you always call inside a derived class constructor? - super(). It calls the parent class constructor and initialises this. Omitting it before accessing this throws a ReferenceError.
  • What happens when you extract a prototype method and call it standalone? - The this binding is lost because this is determined at call time. Fix it with .bind(obj), or call the method directly on the object.
  • What is the difference between hasOwnProperty() and the in operator? - hasOwnProperty() returns true only for properties directly on the object. The in operator returns true for any property anywhere in the prototype chain. Object.hasOwn() is the modern ES2022 replacement for hasOwnProperty().
  • How can you check if a property is directly on an object (not inherited)? - Use obj.hasOwnProperty("prop"). This returns true only if the property exists on the object itself, not on its prototype chain.
  • Where do methods like .push() and .map() come from on arrays? - They live on Array.prototype. Every array instance inherits from Array.prototype through the prototype chain.
  • What happens at the end of the prototype chain? - The last prototype in any chain is Object.prototype. Its own prototype is null, which signals the end of the chain. If a property is not found by then, undefined is returned.
  • Is it safe to modify built-in prototypes like Array.prototype? - Generally no, especially in shared or production codebases. It can cause naming conflicts and unpredictable behaviour in third-party libraries. It is sometimes done in polyfills, but carefully.
  • What is prototypal inheritance vs classical inheritance? - Classical inheritance (like in Java or C++) uses classes as strict blueprints. Prototypal inheritance (like in JavaScript) links objects directly to other objects. JavaScript's class syntax mimics classical style but is still prototypal underneath.