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

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__
andprototype
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 usetoString()
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:
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.