Skip to main content

Command Palette

Search for a command to run...

JavaScript Prototypes – Understanding Prototypal Inheritance

Updated
7 min readView as Markdown

Yesterday, I learned about Object-Oriented Programming in JavaScript, including classes, constructors, methods, inheritance, and the this keyword.

Today, I went deeper into how JavaScript actually implements inheritance.

The key concept is Prototypes.

JavaScript isn't fundamentally a class-based language like Java or C++. It uses a prototype-based object model.

Modern class syntax makes object-oriented programming easier to write, but underneath the surface, JavaScript still relies on prototypes.

Let's dive in.


What is a Prototype?

Every JavaScript object has an internal link to another object called its prototype.

That prototype can provide properties and methods that the object can access.

For example:

const user = {
    name: "Saurabh"
};

console.log(user.toString());

We didn't define toString() inside user.

So where did it come from?

JavaScript looks up the object's prototype and finds the method there.

This is called prototype inheritance.


Prototype Chain

When JavaScript tries to access a property or method, it first checks the object itself.

If it doesn't find it, JavaScript looks at the object's prototype.

If it still doesn't find it, JavaScript continues searching up the prototype chain.

Conceptually:

Object
   ↓
Prototype
   ↓
Prototype's Prototype
   ↓
null

This is called the prototype chain.


Object.prototype

Most ordinary JavaScript objects ultimately inherit from:

Object.prototype

For example:

const user = {
    name: "Saurabh"
};

console.log(
    Object.getPrototypeOf(user)
);

The result is the object's prototype, which for an object literal is generally Object.prototype.


Object.getPrototypeOf()

We can inspect an object's prototype using:

Object.getPrototypeOf(user);

Example:

const user = {
    name: "Saurabh"
};

console.log(
    Object.getPrototypeOf(user)
);

This is useful for understanding the prototype chain.


The __proto__ Property

You may also encounter:

user.__proto__

This accesses the object's prototype through a legacy accessor.

Although you'll see it frequently while learning JavaScript, Object.getPrototypeOf() and Object.setPrototypeOf() are generally preferred for explicit prototype manipulation.

For example:

console.log(
    Object.getPrototypeOf(user)
);

Constructor Functions

Before class syntax became common, JavaScript developers frequently used constructor functions to create similar objects.

Example:

function User(name, age) {

    this.name = name;

    this.age = age;

}

We can create objects using:

const user1 = new User(
    "Saurabh",
    22
);

const user2 = new User(
    "Rahul",
    23
);

The new keyword connects these objects to:

User.prototype

Adding Methods to a Prototype

Instead of creating a new copy of a method for every instance, we can place the method on the constructor's prototype.

User.prototype.greet = function () {

    console.log(
        `Hello ${this.name}`
    );

};

Now:

user1.greet();

user2.greet();

Both objects can use the same prototype method.


Why Use the Prototype?

Consider this:

function User(name) {

    this.name = name;

    this.greet = function () {
        console.log(
            `Hello ${this.name}`
        );
    };

}

Every time we create a new User, a new function is created for greet.

Instead:

function User(name) {

    this.name = name;

}

User.prototype.greet = function () {

    console.log(
        `Hello ${this.name}`
    );

};

Now the method is shared through the prototype.

This is one of the important ideas behind JavaScript's prototype system.


Checking the Prototype

We can verify the relationship:

console.log(
    Object.getPrototypeOf(user1) === User.prototype
);

Output:

true

This tells us that user1 is linked to User.prototype.


Prototype Chain Lookup

Suppose:

const user = {
    name: "Saurabh"
};

We run:

user.toString();

JavaScript roughly searches like this:

Does user have toString?
        ↓
       No
        ↓
Does user.__proto__ have toString?
        ↓
       Yes
        ↓
Execute it

This lookup process is fundamental to understanding JavaScript inheritance.


Constructor's Prototype Property

Functions used as constructors have a .prototype property.

Example:

function User(name) {

    this.name = name;

}

console.log(User.prototype);

We can add methods:

User.prototype.greet = function () {

    console.log(
        `Hello ${this.name}`
    );

};

When an object is created with:

const user = new User("Saurabh");

the object's prototype is connected to:

User.prototype

Class Syntax and Prototypes

This is where yesterday's lesson connects with today's.

When we write:

class User {

    greet() {

        console.log("Hello");

    }

}

the method isn't simply copied separately into every instance.

The class's methods are placed on:

User.prototype

So:

const user = new User();

console.log(
    Object.getPrototypeOf(user) === User.prototype
);

returns:

true

This is why understanding prototypes helps explain what JavaScript classes are actually doing.


Prototypal Inheritance

Objects can inherit from other objects.

One way to create such a relationship is:

const animal = {

    speak() {

        console.log("Animal sound");

    }

};

Create another object based on it:

const dog = Object.create(animal);

Now:

dog.speak();

The dog object doesn't have its own speak() method.

JavaScript finds it through its prototype.


Object.create()

Object.create() allows us to create a new object with a specified prototype.

Example:

const vehicle = {

    start() {

        console.log("Vehicle started");

    }

};

const car = Object.create(vehicle);

car.start();

The prototype relationship is:

car
 ↓
vehicle
 ↓
Object.prototype
 ↓
null

Prototype Inheritance With Constructors

We can also create inheritance between constructor prototypes.

function Animal(name) {

    this.name = name;

}

Animal.prototype.speak = function () {

    console.log(
        `${this.name} makes a sound`
    );

};

Now create a Dog constructor:

function Dog(name, breed) {

    Animal.call(this, name);

    this.breed = breed;

}

Connect the prototypes:

Dog.prototype = Object.create(
    Animal.prototype
);

Dog.prototype.constructor = Dog;

Now add a Dog-specific method:

Dog.prototype.bark = function () {

    console.log(
        `${this.name} says Woof!`
    );

};

Create the object:

const dog = new Dog(
    "Bruno",
    "Labrador"
);

dog.speak();

dog.bark();

The dog can access methods from the Animal prototype.


hasOwnProperty()

Sometimes we need to determine whether a property belongs directly to an object or is inherited.

Example:

const user = {
    name: "Saurabh"
};

console.log(
    user.hasOwnProperty("name")
);

Output:

true

But:

console.log(
    user.hasOwnProperty("toString")
);

returns:

false

because toString is inherited rather than an own property.


Own Properties vs Inherited Properties

Consider:

const user = {
    name: "Saurabh"
};

Own property

name

belongs directly to user.

Inherited property

toString

comes from the prototype chain.

This distinction becomes useful when inspecting and debugging objects.


Prototype vs __proto__

These two terms are often confused.

.prototype

Usually refers to the property on a constructor function that becomes the prototype of instances created with new.

User.prototype

[[Prototype]]

The internal prototype link of an object.

We can access it using:

Object.getPrototypeOf(user)

The legacy:

user.__proto__

also exposes that link, but it is generally better to use the standard Object.getPrototypeOf() API.


Best Practices

✔ Understand prototypes even if you primarily use class syntax.

✔ Prefer Object.getPrototypeOf() over relying on __proto__.

✔ Use prototypes for shared behavior when working with constructor functions.

✔ Don't modify built-in prototypes such as Array.prototype in normal application code.

✔ Use class syntax when it makes your application's object model easier to understand.

✔ Remember that JavaScript's class syntax is built on the prototype system.


My Biggest Takeaway

Today, I learned what's happening underneath JavaScript classes.

The biggest realization was:

JavaScript classes are built on top of prototypes.

When I create an instance:

const user = new User();

the object can access methods through:

user
 ↓
User.prototype
 ↓
Object.prototype
 ↓
null

Understanding this prototype chain makes JavaScript's inheritance model much clearer.

100 Days of Code: My Journey to Becoming a Full Stack Developer

Part 1 of 50

Welcome to my 100 Days of Code journey! In this series, I'll document my daily progress as I learn Full Stack Web Development from the ground up. Every post will cover what I learned, challenges I faced, mistakes I made, and the projects I built. My goal is not just to complete 100 days but to become a better developer through consistency, discipline, and learning in public. Topics I'll cover include: • Git & GitHub • HTML, CSS & JavaScript • React.js • Node.js & Express • MongoDB • APIs • Real-world Projects • AI tools for Developers Whether you're just starting out or revising your fundamentals, I hope this journey helps you learn alongside me. Let's build, learn, and grow together! 🚀

More from this blog

T

TheSaurceCode

73 posts

Documenting my journey to becoming a Full Stack Developer through daily blogs, coding challenges, projects, tutorials, and lessons learned. Learn, build, and grow with me