Understanding and Using Prototypal Inheritance in JavaScript

Understanding and Using Prototypal Inheritance in JavaScript

12 mins read
Oct 31, 2025
Share
editor-page-cover
Content
What is prototypical inheritance?
Revisiting an old example
JavaScript classes and prototypes: How they work together
Safer and modern ways to work with prototypes
Better ways to check properties
Constructor property: Use with care
Avoid prototype pollution and security issues
Modern inheritance with extends, super, and private fields
Framework usage and real-world relevance
Performance and best practices recap
Cons of Prototypal Inheritance
Important terms
__proto__ property
Object.create
Object.prototype.constructor
hasOwnProperty
The Prototype Chain
Keep learning about front-end JavaScript.
Example Code 1: Setting up Prototypical Relationships
Example Code 2: Inheriting Methods
Example Code 3: Three-Tier Inheritance and Scalability
Wrapping up
Continue reading about Javascript

JavaScript is a prototype-based, object oriented programming language. After the ES6 updates, JavaScript allowed for “prototypal inheritance”, meaning that objects and methods can be shared, extended, and copied.

Sharing amid objects makes for easy inheritance of structure (data fields), behavior (functions / methods), and state (data values).

JavaScript is the most common of the prototype-capable languages, and its capabilities are relatively unique. When used appropriately, prototypical inheritance in JavaScript is a powerful tool that can save hours of coding.

Today, we want to get you acquainted with prototypal inheritance in JavaScript to get you up to date with the ES6 capabilities.



Get a full front-end curriculum all in one place

Learn from the best front-end material from across our course library with Educative Paths.

Become a Front End Developer


What is prototypical inheritance? #

Simply put, prototypical inheritance refers to the ability to access object properties from another object. We use a JavaScript prototype to add new properties and methods to an existing object constructor. We can then essentially tell our JS code to inherit properties from a prototype. Prototypical inheritance allows us to reuse the properties or methods from one JavaScript object to another through a reference pointer function.

All JavaScript objects inherit properties and methods from a prototype:

  • Date objects inherit from Date.prototype.
  • Array objects inherit from Array.prototype.
  • Player objects inherit from Player.prototype.

The Object.prototype is on top of the prototype inheritance chain. ​ Date objects, Array objects, and Player objects all inherit from Object.prototype.

svg viewer

Revisiting an old example#

Let’s walk through an example of prototypical inheritance you’re likely familiar with from grade school: all squares are rectangles, but not all rectangles are squares. If we think of this as a JS program, we could say that the rectangle is a prototype to the square: the square inherits all properties of a rectangle (i.e. four-sides and closed), while also adding a new feature (i.e. all sides are the same length).

We could not, however, construct this same concept using the square as a prototype, because there are properties of a square that do not apply to rectangles (i.e. all sides are the same length).

We can see how prototypal inheritance works on the basis of specifying categories within a group from least specific to most – from rectangle to square. In code, this concept can sometimes be lost in the syntax. If you find this happens, speak the relations between objects and listen to where you draw distinctions. If you hear, “all ___ are , but…not all ___ are”, that is where a new prototypical relationship should be added.

JavaScript classes and prototypes: How they work together#

JavaScript has always been a prototype-based language — every object inherits directly from another object, known as its prototype.
The introduction of class syntax in ES2015 didn’t change this model; it simply provided a cleaner, more familiar way to work with prototypes.

Here’s how they map:

class Person {
greet() {
console.log("Hello!");
}
}
const user = new Person();
console.log(Object.getPrototypeOf(user) === Person.prototype); // true

In this example, Person.prototype is where the greet method lives, and user delegates to it through the prototype chain.
This shows that even when you use class, JavaScript still relies on prototypal inheritance behind the scenes.

Safer and modern ways to work with prototypes#

Older examples often use the __proto__ property to manipulate prototypes, but this is considered legacy syntax today.
The modern, recommended alternatives are:

  • Object.create(proto) — Create a new object with a specific prototype.

  • Object.getPrototypeOf(obj) — Access an object’s prototype.

  • Object.setPrototypeOf(obj, proto) — (Use sparingly) Set a prototype explicitly.

Example:

const animal = {
speak() {
console.log("The animal makes a sound.");
}
};
const dog = Object.create(animal);
dog.speak(); // The animal makes a sound.

This approach is cleaner, more predictable, and avoids the performance issues that can occur when mutating prototypes dynamically.

Better ways to check properties#

A common pattern in older codebases is:

obj.hasOwnProperty("key");

This still works, but it’s safer and more modern to use Object.hasOwn(obj, key), introduced in ES2022:

if (Object.hasOwn(obj, "key")) {
// property is directly on the object
}

If you need to check the entire prototype chain, use the in operator instead:

if ("key" in obj) {
// property exists on the object or its prototype
}

Constructor property: Use with care#

Every function’s prototype object has a constructor property that points back to the function that created the instance.
While this can be useful for reflection, it’s not commonly used in modern code — especially now that we rely more on classes and modules.

Also, avoid using wrapper objects like:

new Number(5)
new Boolean(true)

These behave unexpectedly and can cause bugs.
Instead, stick to primitives (5, true, "hello") and use prototypes for objects that actually need them.

Avoid prototype pollution and security issues#

Prototype inheritance can introduce security risks if not handled carefully.
Prototype pollution occurs when user-controlled input modifies the base Object.prototype, potentially affecting all objects in the environment.

To mitigate this:

  • Use Object.create(null) for objects meant to be used as maps or dictionaries — this ensures they have no prototype.

  • Always validate keys before assigning user data to objects.

  • Never trust user input when extending prototypes.

Example:

const safeMap = Object.create(null);
safeMap["key"] = "value";

This object has no prototype chain and is immune to prototype pollution attacks.

Modern inheritance with extends, super, and private fields#

The class syntax now supports advanced features like inheritance, super calls, and private fields — all of which still use prototypes under the hood:

class Animal {
speak() {
console.log("Generic sound");
}
}
class Dog extends Animal {
#name = "Rex"; // private field
speak() {
super.speak(); // calls Animal's speak()
console.log(`${this.#name} barks`);
}
}
const d = new Dog();
d.speak(); // Generic sound
// Rex barks

This example shows how modern JavaScript still relies on prototype chains, but now offers more expressive and secure ways to build hierarchies.

Framework usage and real-world relevance#

Prototypal inheritance powers many modern JavaScript frameworks and libraries — even if you don’t interact with it directly.
React components, Node.js built-ins, and many standard APIs rely on prototypes for method sharing and polymorphism.

However, avoid using outdated references like AngularJS (which is no longer maintained).
Instead, connect the concept to current tools like React, Vue, and modern Node.js applications — where prototypes still matter, even if hidden beneath higher-level abstractions.

Performance and best practices recap#

  • Favor Object.create() over modifying prototypes dynamically.

  • Use Object.hasOwn() instead of hasOwnProperty().

  • Avoid __proto__ and direct prototype mutation.

  • Consider Object.create(null) for dictionary-like objects.

  • Embrace class syntax for readability — but remember it’s still prototypes under the hood.

These best practices ensure your JavaScript code is maintainable, performant, and secure — while still leveraging the power of prototypal inheritance.

Cons of Prototypal Inheritance#

Prototypical inheritance clearly has a lot of benefits for JavaScript programmings, but, like all tools, it does have limitations. Let’s take a look at the key downsides to look out for as you write a prototype-based program:

  • Inheritance cannot flow in circles as this will create an error. For example, if user linked premiumFamily as a prototype in the above program, an error would occur as this would create a loop.

  • Objects cannot inherit from multiple prototypes. As we saw above, they can inherit multiple object’s properties through a chain, however another object linked as a prototype explicitly will cause an error. This is the case even if the additional prototype is within the same chain. For example, familyPremium could not have explicit links to both premiumUser and user.

  • Prototypical relationships can only be made to objects. This is because the __proto__ function works as a forwarder, directing the program where to find the value it is looking for. As the program either knows where to look or it doesn’t, the function can be only either null or an object. All other types will be discarded.


Important terms#

__proto__ property#

In Javascript, every object has its own hidden, internal property, [[Prototype]]. We can access that [[Prototype]] using the __proto__ property. This calls the program to mark the template object as a hidden type. JavaScript objects must be linked to this prototype object. Now, an object’s properties can be accessed by the inheritor object.

Let’s take a look at the syntax for accessing and setting the [[Prototype]] property of an object.

//using __proto__ to access and set the [[Prototype]] of "anObject"
anObject.__proto__ = someotherObject

Object.create#

JavaScript ECMAScript 5 comes with the function Object.create( ). This method can be used to replacenew. We can use it to create an empty object based on a defined prototype and then assign it to a different prototype. Take a look at the syntax:

Object.create(proto, [propertiesObject])

Object.create methods can accept two arguments: propertiesObject and prototypeObject.


Object.prototype.constructor#

All objects have a constructor property. If an object is created without the using a constructor function, it will have a constructor property. The constructor property will return a reference to the object’s Object constructor function. It will return 1, true1, and ”test”. Take a look at an example below.

let o = {} o.constructor === Object // true 

let o = new Object o.constructor === Object // true 

let a = [] a.constructor === Array // true 

let a = new Array a.constructor === Array // true 

let n = new Number(3) n.constructor === Number // true

hasOwnProperty#

Using hasOwnProperty, we can test if an object contains a certain prototype property; the method will return true or false depending. This will help you clarify if an object has its own property or if it is inheriting instead. Take a look at the syntax below:

obj.hasOwnProperty(prop)

The Prototype Chain#

Prototypal inheritance uses the concept of prototype chaining. Let’s explore that concept. Every object created contains [[Prototype]], which points either to another object or null. Envision an object C with a [[Prototype]] property that points to object B. Object B’s [[Prototype]] property points to prototype object A. This continues onward, forming a kind of chain called the prototype chain.

This concept is used when searching our code. When we need to find a property in an object, it is first searched for in the object, and if not found, it is searched for on that object’s prototype, and so on. Thus, the entire prototype chain is traversed until the property is found or null is reached.

In the following sections, we’ll take a look at some implementations using the handling of accounts in a streaming service.



Keep learning about front-end JavaScript.#

Master JavaScript and other front-end technologies with hands-on practice.

Educative Paths let you access all our best front-end lessons pulled from across our course library.

Become a Front End Developer


Example Code 1: Setting up Prototypical Relationships #

For this first example, we’ll write a simple prototypical relationship between two objects, user and premiumUser, using the ._proto_ function. Each of these objects has their own properties which would be shared among all accounts at that tier: all users have access to stream shows, showAccess = true, and all premiumUsers have advertisements disabled, ads = false.

Javascript (babel-node)
let user = { //create the user object
showAccess: true //create and set showAccess property of user
};
let premiumUser = { //repeat of the above for this object
ads: false
};
premiumUser.__proto__ = user; //user is the prototype of premiumUser
console.log(premiumUser.showAccess); // "true"

The prototypical relationship here ensures that premiumUser inherits the showAccess property set from user without having to set it manually at the premium tier.

To check that this has inherited properly, we add a line to have the console print the current value of showAccess for premiumUser. As it returns true, we can see that premiumUser has inherited this property from user.

Example Code 2: Inheriting Methods #

Prototypal Inheritance can be used not only to inherit properties from other objects but methods as well.

In the example below, we build off our previous code and now add email and IDnumber properties to user, tracking account info for this user, as well as a setter method, accountInfo which when called will parse a passed string, setting email and IDnumber to the new passed values.

Javascript (babel-node)
let user = {
email: "educative@gmail.com", //create and set email property
IDnumber: "#12345", //create and set the Idnumber property
showAccess: true,
set accountInfo(value) { //Setter method to change values of email and ID
[this.email, this.IDnumber] = value.split(" ");
},
// defined method in the prototype
get accountInfo() {
return `${this.email} ${this.IDnumber}`;
}
};
let premiumUser = {
__proto__: user,
ads: false
};
// calls the inherited getter method
console.log(premiumUser.accountInfo); // "educative@gmail.com #12345"
premiumUser.accountInfo = "blogreader2020@hotmail.com #54321"; // calls the inherited setter method
console.log(premiumUser.accountInfo); // "blogreader2020@hotmail.com #54321"
//ID and Email values are now different for each object
console.log(user.accountInfo); // "educative@gmail.com #12345"

The key section of this example is the calling of the three methods at the bottom. Each of these methods are defined under the user object and therefore would usually be inaccessible by premiumUser. However, because user is the prototype of premiumUser, all methods and properties of user are shared with any inheritor objects.

From the final two methods, we also see how the value of the properties shared are not stagnant but can be set to new values regardless of the properties’ values in the prototype.

Example Code 3: Three-Tier Inheritance and Scalability #

As you might have noticed, the examples above allow for only one account in user and one account in premiumUser. To introduce much needed scalability, we pivot from using these objects as variables and instead use them as an equivalent to classes.

Instead of changing properties’ values, we create new objects for each account, setting the prototype for that new object based on the tier of the account.

In the example below, the object me will be my account. This object then calls the inherited setter method to set values for the email and IDnumber property exclusive to this account, and set its tier by making the newly added familyPremium object as its prototype.

While this is an example using a single account object, this procedure could be scaled to assign the correct properties to any number of objects.

Javascript (babel-node)
let user = {
email: "missing email", //fillers to reveal errors in inheritance at print
IDnumber: "missing ID number",
showAccess: true,
set accountInfo(value) {
[this.email, this.IDnumber] = value.split(" ");
},
get accountInfo() {
return `${this.email} ${this.IDnumber}`;
}
};
let premiumUser = {
__proto__: user,
Ads: false
};
let familyPremium = { //our new third tier of membership
__proto__: premiumUser, // in an inheritance chain with prior two objects
multipleDevices: true
};
let me = { //an object for an individual user, me in this case
__proto__: familyPremium, //inheritance to decide class
email: "mymail@outlook.com", //setting property values exclusive to this object
IDnumber: "#67899"
};
console.log(me.multipleDevices); // true
console.log(me.accountInfo); // mymail@outlook.com #67899
//Least specific to most: not all user accounts are premium accounts, not all premium accounts are family premium accounts.

Even with three levels of inheritance, we can see that me has access to data throughout the chain, from the immediately inherited multipleDevices property to the inherited accountInfo method, defined at the top of its chain in user.

Regardless of how many levels the inheritance chain has, all information from previous levels are retained and accessible.

Through the use of prototypical inheritance, we’re able to create a program in which new accounts can be added and assigned established properties in only a few lines of code rather than having to set manually.

It also allows for ease of adaptability of those properties. If we could change properties of all inheritor accounts by only changing the properties in the prototype.

As a result, we get a program which is scalable, adaptable, and efficient in execution, all possible thanks to a prototype-based approach.


Wrapping up #

Like JavaScript itself, prototypical inheritance is an invaluable tool in the world of website development and server management.

Specifically, prototypes and prototypical inheritance are commonly used in many web application frameworks, such as AngularJS, to allow sharing of common behavior and state among similar components.

Continue to learn about inheritance with Educative’s course Learn Object-Oriented Programming in JavaScript. This course has in-depth explanations of advanced OOP topics like prototyping, chaining, restricted classes. By the end, you’ll have hands-on project experience with OOP techniques to use in your next JavaScript programs.


Continue reading about Javascript#


Written By:
Ryan Thelin