JavaScript Prototype and __proto__ - Do you know how it works?

TL;DR: In JavaScript, all inheritance is prototypal — meaning every object inherits from another object via a chain called __proto__. Behind the scenes, even classes and arrays rely on this mechanism. This post breaks down what __proto__ and prototype really are, how they differ, how inheritance chains are formed, and how constructors pass down behavior. If you've ever been confused about how objects share methods or why you can use toString() on everything — this one's for you. Introduction Welcome to Episode 2 of the series “Do You Know How It Works?” If you're following along, you'll remember that everything started when I wrote a post on Arrow Functions (which turned out to be Episode 0). The response to that post — full of insightful questions — made me realize that many devs are still unsure about the core mechanics of JavaScript. So here we are, continuing the journey. Today’s topic is one of the most misunderstood (and underexplained) aspects of JavaScript: the difference between __proto__ and prototype. If you’ve ever wondered: Why your objects can use .toString() even if you never defined it, What’s really happening when you use class, or What Array.prototype actually means... Then this post is for you. Let's dive into the real mechanics of JavaScript inheritance. As we saw in the inheritance topic, JavaScript handles inheritance differently compared to other programming languages. We're talking about prototypal inheritance. But what does that mean in practice? Let's take a closer look. const person = {}; console.log(person); // Object { __proto__: Object } Here, we declare an empty person object. But if we inspect its contents, we see it's not truly empty— it contains a __proto__ attribute (sometimes shown as in the browser),even though we did not explicitly declared it. Well, to understand that, we have to go a bit deeper. Let's do that: person.name = "Alan Turing"; person.age = 41; console.log(person.name); // Alan Turing console.log(person.age); // 41 console.log(person.nationality); // undefined console.log(person.speak); // undefined The results here are as expected — everything we didn’t explicitly define returns undefined. But there are some exceptions: person.toString; // function toString(); person.valueOf; // function valueOf(); Even though we didn’t define these functions, they don’t return undefined. In fact, if we run them, they produce a result. But why is that? Where do they come from? __proto__ Actually, whenever we declare a variable or constant with an object as its value — no matter how it’s created (const obj = {}, const obj = new Object(), or Object.create({})) — it will always have a __proto__ property. This holds everything the object inherits for being an instance of Object (capital O). This isn’t exclusive to objects. Depending on the variable type you create, it will inherit from a different constructor. Constructors are basically JavaScript’s foundations — what every type is built from. For example: const cars = []; console.log(cars); // Array [length: 0, __proto__: Array[]] When creating an array, we can already see it has some specific features: a length attribute, and its __proto__ is Array[]. That means it was built using the Array constructor, which defines that arrays have a length and some built-in methods. cars.push("Aston Martin"); console.log(cars.length); // 1 console.log(cars.pop()); // Aston Martin console.log(cars.length); // 0 That’s why we can use methods like Array.push() and Array.pop(). And that’s why documentation often shows these methods prefixed with Array.—they’re methods of the Array class that all arrays inherit from. But __proto__ isn’t exclusive to complex types like objects, arrays, or sets. Every type in JavaScript is defined by a prototype. const fruit = "Banana"; console.log(fruit.__proto__); // String { "" } const number = 10; console.log(number.__proto__); // Number { 0 } const boolean = true; console.log(boolean.__proto__); // Boolean { false } All primitive types have their own constructors and inherit their own attributes and methods. Also, inside the constructor output, you’ll often see a value in curly braces, such as "" , 0, or false — those represents the default primitive value of that type. So, if we create a variable using those constructors without passing any value, it will be initialized with that primitive default: const stringTest = new String(); const numberTest = new Number(); const boolTest = new Boolean(); console.log(stringTest.valueOf()); // "" console.log(numberTest.valueOf()); // 0 console.log(boolTest.valueOf()); // false Even more interesting, each of these constructors (String, Number, Boolean, etc.) also has its own __proto__. const someVar = ""; console.log(someVar.__proto__.__proto__); // Object { } When we open that last __proto__, we see that it’s the same as what we get when we crea

Apr 7, 2025 - 14:46
 0
JavaScript Prototype and __proto__ - Do you know how it works?

TL;DR:

In JavaScript, all inheritance is prototypal — meaning every object inherits from another object via a chain called __proto__. Behind the scenes, even classes and arrays rely on this mechanism. This post breaks down what __proto__ and prototype really are, how they differ, how inheritance chains are formed, and how constructors pass down behavior. If you've ever been confused about how objects share methods or why you can use toString() on everything — this one's for you.

Introduction

Welcome to Episode 2 of the series “Do You Know How It Works?”

If you're following along, you'll remember that everything started when I wrote a post on Arrow Functions (which turned out to be Episode 0). The response to that post — full of insightful questions — made me realize that many devs are still unsure about the core mechanics of JavaScript.

So here we are, continuing the journey.

Today’s topic is one of the most misunderstood (and underexplained) aspects of JavaScript: the difference between __proto__ and prototype.

If you’ve ever wondered:

  • Why your objects can use .toString() even if you never defined it,
  • What’s really happening when you use class, or
  • What Array.prototype actually means...

Then this post is for you. Let's dive into the real mechanics of JavaScript inheritance.

As we saw in the inheritance topic, JavaScript handles inheritance differently compared to other programming languages. We're talking about prototypal inheritance. But what does that mean in practice? Let's take a closer look.

const person = {};
console.log(person); // Object { __proto__: Object }

Here, we declare an empty person object. But if we inspect its contents, we see it's not truly empty— it contains a __proto__ attribute (sometimes shown as in the browser),even though we did not explicitly declared it.

Well, to understand that, we have to go a bit deeper. Let's do that:

person.name = "Alan Turing";
person.age = 41;
console.log(person.name); // Alan Turing
console.log(person.age); // 41
console.log(person.nationality); // undefined
console.log(person.speak); // undefined

The results here are as expected — everything we didn’t explicitly define returns undefined. But there are some exceptions:

person.toString; // function toString();
person.valueOf; // function valueOf();

Even though we didn’t define these functions, they don’t return undefined. In fact, if we run them, they produce a result. But why is that? Where do they come from?

__proto__

Actually, whenever we declare a variable or constant with an object as its value — no matter how it’s created (const obj = {}, const obj = new Object(), or Object.create({})) — it will always have a __proto__ property. This holds everything the object inherits for being an instance of Object (capital O).

This isn’t exclusive to objects. Depending on the variable type you create, it will inherit from a different constructor. Constructors are basically JavaScript’s foundations — what every type is built from.

For example:

const cars = [];
console.log(cars); // Array [length: 0, __proto__: Array[]]

When creating an array, we can already see it has some specific features: a length attribute, and its __proto__ is Array[]. That means it was built using the Array constructor, which defines that arrays have a length and some built-in methods.

cars.push("Aston Martin");
console.log(cars.length); // 1
console.log(cars.pop());  // Aston Martin
console.log(cars.length); // 0

That’s why we can use methods like Array.push() and Array.pop(). And that’s why documentation often shows these methods prefixed with Array.—they’re methods of the Array class that all arrays inherit from.

But __proto__ isn’t exclusive to complex types like objects, arrays, or sets. Every type in JavaScript is defined by a prototype.

const fruit = "Banana";
console.log(fruit.__proto__); // String { "" }

const number = 10;
console.log(number.__proto__); // Number { 0 }

const boolean = true;
console.log(boolean.__proto__); // Boolean { false }

All primitive types have their own constructors and inherit their own attributes and methods. Also, inside the constructor output, you’ll often see a value in curly braces, such as "" , 0, or false — those represents the default primitive value of that type.

So, if we create a variable using those constructors without passing any value, it will be initialized with that primitive default:

const stringTest = new String();
const numberTest = new Number();
const boolTest = new Boolean();

console.log(stringTest.valueOf()); // ""
console.log(numberTest.valueOf()); // 0
console.log(boolTest.valueOf());   // false

Even more interesting, each of these constructors (String, Number, Boolean, etc.) also has its own __proto__.

const someVar = "";
console.log(someVar.__proto__.__proto__); // Object { }

When we open that last __proto__, we see that it’s the same as what we get when we create a plain empty object. So we can conclude: String inherits from Object, and a variable created from String inherits from String.

This confirms that even types like String eventually inherit from Object, forming a __proto__ chain (called Prototype chain) that always leads back to JavaScript’s root: Object. Let’s explore that concept:

const being = {
  type: "Living Being"
};

const species = Object.create(being);
species.name = "Human";

const person = Object.create(species);
person.age = 25;

console.log(person);
/* 
Object { 
  age: 25,
  __proto__: Object { 
    name: "Human",
    __proto__: Object {
      type: "Living Being",
      __proto__: Object {...}
    }
  }
}
*/

When inspecting the person object, we see it inherits everything from species (which was used as a blueprint) and from being (used to create species) — and ultimately from Object.

Let’s go a bit further:

const person2 = Object.create(species);
person2.name = "Dwight";

console.log(person.name);  // Human
console.log(person2.name); // Dwight

When printing person.name, we get "Human" from species.name. But person2.name is "Dwight", even though it was created from species.

That’s because person2 received its own name attribute. When we call person.name, it searches its own object, finds nothing, and walks down the __proto__ chain to get the value. But person2 already has a name, so it stops there.

console.log(person2);
/*
Object {
  name: "Dwight",
  __proto__: Object {
    name: "Human",
    __proto__: Object {...}
  }
}
*/

If we wanted to change the name of species using person2 as a reference, we’d need to modify the name inside its __proto__. That would change species.name and affect all objects inheriting from it.

person2.__proto__.name = "Elf";
console.log(person.name);    // Elf
console.log(species.name);   // Elf

This Also Works with Classes

class Vehicle {
  move() {
    return "Moving";
  }
}

class Car extends Vehicle {
  accelerate() {
    return "Vroom";
  }
}

class RaceCar extends Car {
  compete() {
    return "Jump Start!";
  }
}

const mclaren = new RaceCar();

console.log(mclaren); // Object { }

mclaren.move();       // "Moving"
mclaren.accelerate(); // "Vroom"
mclaren.compete();   // "Jump Start!"
mclaren.winRace();    // undefined

If we inspect mclaren, at the top level we see it was created from the RaceCar constructor. Its __proto__ is Car, and the next level is Vehicle, and finally Object. Something like this:

Protype Chain

Also, mclaren can do everything a RaceCar, Car, and Vehicle can do. But if we try to access something that doesn’t exist anywhere in that inheritance chain, we get undefined.

prototype

Let’s set __proto__ aside for a second. What is prototype and how does it differ?

It’s common to see something like Array.prototype.concat() in docs. But what exactly is prototype?

Well, the prototype property only exists on constructor functions or classes (we’ll discuss constructor, classes and some other similar topics in a future post).

Check this out:

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

const person = new Person("Michael Jackson");

console.log(person); // Object { name: "Michael Jackson" }

person.__proto__ === Person.prototype; // true

What we see here is that the Person(name) function defines how an object should be constructed. In this case, we pass a name parameter to generate a Person object.

Also, we can observe that a class’s prototype is passed to the object as __proto__. So, if we add or modify something on the class’s prototype, that change is reflected across all objects of that class.

The reverse is also true—modifying the __proto__ of one instance can reflect on the prototype of the class, and on all other instances.

In essence, __proto__ and prototype refer to the same object—just accessed in different ways.

But to summarize, those are the differences:

__proto__

→ Exists on every object instance

→ Points to the object it inherits from

→ Used at runtime to resolve properties and methods up the inheritance chain

→ You rarely set this manually (though it’s possible)

prototype

→ Exists only on constructor functions and classes

→ Defines what new instances created with new will inherit

→ You use it to add or override shared methods

→ It becomes the __proto__ of instances created by that constructor

Final Thoughts

Understanding __proto__ and prototype is a big step toward mastering how JavaScript works under the hood. It's not just theory — it's the backbone of how objects communicate, inherit behavior, and stay memory-efficient.

Whether you're working with plain objects, using classes, or building something with frameworks like React or Vue, this concept is always there — silently powering your code.

My tip?

Experiment with these concepts, inspect your objects in the browser console, and try building your own inheritance chains using Object.create(). You'll be surprised how much clearer JavaScript becomes once this "invisible" layer clicks.