Achieving Inheritance in JavaScript Without extends: Understanding "__proto__" in JS

Photo by Lea Kobal on Unsplash

Achieving Inheritance in JavaScript Without extends: Understanding "__proto__" in JS

In this blog, you'll develop a clear understanding of how the extends keyword functions and how inheritance works under the hood.

In JavaScript, inheritance is usually done using the extends keyword in classes. But did you know that you can achieve inheritance without extends? This is possible using the __proto__ property, which links one object to another.

In this blog, we will break down how __proto__ works with super simple examples so that even beginners can understand it!

What is __proto__?

Every JavaScript object has a hidden property called __proto__, which points to the prototype of another object. This is how JavaScript enables prototype-based inheritance.

When you try to access a property/method that’s not in the object, JavaScript follows the prototype chain (__proto__) to find it in its parent. Lets discuss this in details:

Lets understand what happens when you try to access a property which is not defined in the class.

🛠️ Steps Explained in the Flowchart:

  1. Check Inside the Object Itself
  1. Check Inside the Class Prototype (__proto__)
  1. Check Inside Object.prototype
  1. Reached null (End of Prototype Chain)

Inheritance Without extends Using __proto__

Let's say we have two classes: Person and Student. Normally, we would write class Student extends Person, but here, we’ll use __proto__ instead.

Step 1: Create Classes

// Here I am creating 2 classes 
class Person {
    constructor(name) {
        this.name = name;
    }
    greet() {
        console.log(`Hello, my name is ${this.name}`);
    }
}
class Student {
    constructor(name, grade) {
        this.name = name;
        this.grade = grade;
    }
    study() {
        console.log(`${this.name} is studying in grade ${this.grade}.`);
    }
}

So far, Person has a method greet(), and Student has a method study(). But Student doesn’t inherit from Person yet.

We want every student to also have access to greet() from Person.
We can achieve this by setting the prototype of Student.prototype to Person.prototype:

// Overriding the __proto__ property of Student by Person.prototype.
Student.prototype.__proto__ = Person.prototype;

Now, when a Student object tries to access a method that isn’t in Student, JavaScript will look inside Person.


Step 3: Create Objects and Test

const student1 = new Student("Amit", 10);

student1.study();  // ✅ Output: Amit is studying in grade 10.
student1.greet();  // ✅ Output: Hello, my name is Amit

What Happened Here?

  • student1.study() works because study() is in Student.

  • student1.greet() works even though greet() is not in Student, because JavaScript looks inside Person via __proto__.

Now, we have successfully achieved inheritance without using extends! 🚀


Example 2: Multi-Level Inheritance Without extends

Let’s go a step further. Suppose we create another class, HighSchoolStudent, which should inherit from Student.

class HighSchoolStudent {
    constructor(name, grade, stream) {
        this.name = name;
        this.grade = grade;
        this.stream = stream;
    }

    prepareForExams() {
        console.log(`${this.name} is preparing for final exams in ${this.stream}.`);
    }
}

// overriding HighSchoolStudent prototype to Student's
HighSchoolStudent.prototype.__proto__ = Student.prototype;

HighSchoolStudent will inherit from Student, and indirectly from Person.

Step 3: Create Objects and Test

const highSchoolStudent1 = new HighSchoolStudent("Neha", 12, "Science");

highSchoolStudent1.prepareForExams();  // ✅ Output: Neha is preparing for final exams in Science.
highSchoolStudent1.study();            // ✅ Output: Neha is studying in grade 12.
highSchoolStudent1.greet();            // ✅ Output: Hello, my name is Neha.

🎯 How Prototype Chain Works Here
When we call highSchoolStudent1.greet(), JavaScript searches:

HighSchoolStudent → Student → Person → Object.prototype → null

Since greet() is found inside Person, JavaScript calls it successfully!


Checking the Prototype Chain

If you want to check the prototype links, use this:

console.log(HighSchoolStudent.prototype.__proto__ === Student.prototype); // ✅ true
console.log(Student.prototype.__proto__ === Person.prototype); // ✅ true
console.log(Person.prototype.__proto__ === Object.prototype); // ✅ true
console.log(Object.prototype.__proto__ === null); // ✅ true

This confirms that JavaScript follows the prototype chain step by step.

Conclusion

✅ We achieved inheritance without using extends, just like JavaScript does behind the scenes.
__proto__ allows us to manually link one class to another.
✅ JavaScript follows the prototype chain to find missing methods.

Now that you understand how extends works internally, try using __proto__ in your own projects!