Understanding Prototypes, Prototype Chaining, and Prototype Inheritance in JavaScript

Introduction

Have you ever wondered how strings, arrays, or objects "know" which methods they can use — like .toUpperCase() for strings or .sort() for arrays? These methods are never manually defined in your own code, yet they’re available out of the box.

This behaviour is made possible by JavaScript’s prototype system, more specifically through prototype inheritance.

In JavaScript, every object can inherit properties from another object, known as its prototype. This system enables a powerful and flexible object model without relying on traditional class-based inheritance, like in languages such as Java or Python.


How to Access a Prototype’s Properties and Methods

Each object in JavaScript contains an internal [[Prototype]] property — a reference to another object from which it can inherit methods and properties.

Although [[Prototype]] is internal and not directly accessible in code, JavaScript provides ways to work with it:

Using Object.getPrototypeOf()

const arr = [1, 2, 3];
console.log(Object.getPrototypeOf(arr)); // Logs Array.prototype

Deprecated but Common: __proto__

console.log(arr.__proto__); // Also logs Array.prototype

The prototype of an object contains methods like .push(), .map(), and .filter() for arrays — even though these methods aren’t explicitly defined on the array instance itself.


The Prototype Chain

When you try to access a property or method on an object:

  1. JavaScript first checks if the property exists directly on the object.

  2. If not, it looks at the object’s prototype.

  3. Then, it looks at the prototype’s prototype.

  4. This continues until it either finds the property or reaches the end of the chain (null).

This series of lookups is known as the prototype chain.

Let’s explore how this works with different JavaScript types.


Prototype Chain for Plain Objects

const user = { name: "Jake" };

console.log(Object.getPrototypeOf(user));                          // Object.prototype
console.log(Object.getPrototypeOf(Object.getPrototypeOf(user)));  // null

Chain:

user → Object.prototype → null

Prototype Chain for Arrays

const arr = [1, 2, 3];

console.log(Object.getPrototypeOf(arr));                                 // Array.prototype
console.log(Object.getPrototypeOf(Object.getPrototypeOf(arr)));         // Object.prototype
console.log(Object.getPrototypeOf(Object.getPrototypeOf(Object.getPrototypeOf(arr)))); // null

Chain:

arr → Array.prototype → Object.prototype → null

Prototype Chain for Functions

function greet() {
  console.log("Hello");
}

console.log(Object.getPrototypeOf(greet));                               // Function.prototype
console.log(Object.getPrototypeOf(Object.getPrototypeOf(greet)));       // Object.prototype
console.log(Object.getPrototypeOf(Object.getPrototypeOf(Object.getPrototypeOf(greet)))); // null

Chain:

greet → Function.prototype → Object.prototype → null

Prototype Chain for Strings

const str = "hello";

console.log(Object.getPrototypeOf(str));                                 // String.prototype
console.log(Object.getPrototypeOf(Object.getPrototypeOf(str)));         // Object.prototype
console.log(Object.getPrototypeOf(Object.getPrototypeOf(Object.getPrototypeOf(str)))); // null

Chain:

str → String.prototype → Object.prototype → null

Demonstrating Prototypical Inheritance

Let’s look at a custom example that illustrates how one object can inherit properties and methods from another using prototypes.

let basePerson = {
    name: 'Ananya',
    age: 28,
    introduce: function () {
        console.log(`Hi, I'm ${this.name} and I'm ${this.age} years old.`);
    }
};

let developer = {
    name: 'Rohit'
};

// Manually setting the prototype (not recommended for production use)
developer.__proto__ = basePerson;

// developer can now access methods and properties from basePerson
developer.introduce(); // Output: Hi, I'm Rohit and I'm 28 years old.

console.log(developer.name); // Rohit
console.log(developer.age);  // 28

What’s happening here?

  • developer does not have an introduce() method or age property.

  • When developer.introduce() is called, JavaScript looks up the method in the developer object.

  • It doesn’t find it there, so it checks the prototype — basePerson.

  • It finds the method and calls it in the context of developer, so this.name is "Rohit" and this.age is inherited as 28.

This is a practical example of prototypical inheritance in action.


JavaScript: A Prototype-Based Language

JavaScript is not a class-based language like Java or Python. Instead, it uses prototypal inheritance as its core mechanism for inheritance and reuse.

Here’s how it works:

  • Every object has an internal reference to its prototype ([[Prototype]]).

  • This forms a chain of objects.

  • At the top of every chain is Object.prototype, and its prototype is null.

Even arrays, strings, and functions ultimately inherit from Object.prototype.


JavaScript Classes: Just Syntactic Sugar

With ES6, JavaScript introduced the class keyword to provide a cleaner, more familiar syntax for object-oriented code. However, JavaScript classes are just a wrapper over the prototype system.

Using class syntax

class Alien {
  constructor(name, phrase) {
    this.name = name;
    this.phrase = phrase;
  }

  sayPhrase() {
    console.log(this.phrase);
  }

  fly() {
    console.log("Zzzzzziiiiiinnnnnggggg!!");
  }
}

const alien1 = new Alien("Ali", "I'm Ali the alien!");
alien1.sayPhrase(); // I'm Ali the alien!

Equivalent using constructor and prototypes

function Alien(name, phrase) {
  this.name = name;
  this.phrase = phrase;
}

Alien.prototype.sayPhrase = function () {
  console.log(this.phrase);
};

Alien.prototype.fly = function () {
  console.log("Zzzzzziiiiiinnnnnggggg!!");
};

const alien1 = new Alien("Ali", "I'm Ali the alien!");
alien1.sayPhrase(); // I'm Ali the alien!

Both patterns behave identically — the class syntax is simply more readable and structured.


Summary

  • JavaScript uses prototypal inheritance, not class-based inheritance.

  • Every object has a [[Prototype]], forming a prototype chain for lookups.

  • Property and method access walks up the chain until it’s found or null is reached.

  • Built-in objects like arrays, strings, and functions all follow this inheritance path.

  • JavaScript classes (class) are syntactic sugar over this prototype system.

Thank you for reading!
If you found this blog helpful, feel free to leave a comment or share it with others.
**Follow me for more deep dives into JavaScript and web development topics.

1
Subscribe to my newsletter

Read articles from Deepthi Purijala directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Deepthi Purijala
Deepthi Purijala

Full Stack Developer with hands-on experience of more than 1 year. Proficient in both Back-end and Front-end technologies, with a strong commitment to delivering high-quality code