Prototypes & Inheritance in JavaScript

Most objects in JavaScript have an internal [[Prototype]] link to another object. That hidden link points to the object's prototype. Some objects, like ones created with Object.create(null), have null instead.

When you try to use a property or method on an object, JavaScript first looks at the object itself. If it doesn't find it there, it follows that hidden link and checks the prototype. If it still can't find it, it goes up another level. This is called the prototype chain.

The standard way to read that internal link is Object.getPrototypeOf(). __proto__ is a legacy accessor that exposes the same relationship on many objects.

javascript

const animal = {
  speak() {
    console.log("Some generic sound...");
  }
};

const dog = Object.create(animal); // dog's prototype is animal
dog.name = "Rex";

dog.speak(); // "Some generic sound..." - found on the prototype
console.log(dog.name); // "Rex" - found on dog itself

animal.type = "animal";
console.log(dog.type); // "animal" - not on dog, so JavaScript checks the prototype
dog.type = "dog";
console.log(dog.type); // "dog" - own properties are checked first

// Check the prototype link
console.log(Object.getPrototypeOf(dog) === animal); // true
console.log(dog.__proto__ === animal); // true (same relationship, legacy accessor)

In the example above, speak() is not on the dog object directly. But because dog's prototype is animal, JavaScript finds it there. That's the prototype chain in action.

Beginner mental model: delegation

A prototype is not a parent object that copies properties into the child. It is a fallback object that the child delegates to when a property is missing. The child keeps its own data; shared methods can stay on the prototype and be reused by many objects.

javascript

const sharedActions = {
  greet() {
    return `Hello, ${this.name}`;
  }
};

const firstUser = Object.create(sharedActions);
firstUser.name = "Ava"; // own data

console.log(firstUser.greet()); // delegated method uses firstUser as this
console.log(Object.hasOwn(firstUser, "greet")); // false
console.log(Object.hasOwn(sharedActions, "greet")); // true

The prototype chain is a series of linked objects. It goes: your object → its prototype → that prototype's prototype → and so on, all the way up to null. When JavaScript hits null, it stops looking and returns undefined.

This is why things like .toString() and .hasOwnProperty() work on most ordinary objects in JavaScript. They live way up the chain on Object.prototype.

javascript

const obj = { title: "Dashboard" };

// Older-compatible own-property checks
console.log(Object.prototype.hasOwnProperty.call(obj, "title")); // true
console.log(Object.prototype.hasOwnProperty.call(obj, "icon"));  // false

// toString also lives on Object.prototype
console.log(obj.toString()); // "[object Object]"

// Let's trace the chain manually
console.log(Object.getPrototypeOf(obj) === Object.prototype); // true
console.log(Object.getPrototypeOf(Object.prototype));         // null - end of chain

// A deeper chain example
function Vehicle() {}
function Car() {}
Car.prototype = Object.create(Vehicle.prototype);
Car.prototype.constructor = Car; // restore the expected constructor reference

const myCar = new Car();

console.log(myCar instanceof Car);     // true
console.log(myCar instanceof Vehicle); // true - it's in the chain!
console.log(Object.getPrototypeOf(Object.getPrototypeOf(myCar)) === Vehicle.prototype); // true

Most ordinary objects eventually reach Object.prototype near the top of the chain. Objects created with Object.create(null) are the exception because they have no prototype.

Object.create(proto) creates a brand new object whose prototype is proto. It is one of the cleanest ways to set up inheritance in JavaScript.

You can also pass a second argument to Object.create() with property descriptors to define properties directly on the new object at the same time.

javascript

const componentProto = {
  render() {
    console.log(`Rendering ${this.name}`);
  },
  describe() {
    console.log(`Version ${this.version}`);
  }
};

// Create a new object with componentProto as its prototype
const app = Object.create(componentProto);
app.name = "Dashboard";
app.version = "2.1";

app.render();   // "Rendering Dashboard"
app.describe(); // "Version 2.1"

// app itself only has name and version
console.log(Object.keys(app)); // ["name", "version"]

// But it can still use render and describe via the prototype
console.log(Object.getPrototypeOf(app) === componentProto); // true

// Using the second argument to set own properties at creation time
const settingsPage = Object.create(componentProto, {
  name:    { value: "Settings", writable: true, enumerable: true },
  version: { value: "1.0", writable: true, enumerable: true }
});
settingsPage.render(); // "Rendering Settings"

Object.create(null) creates an object with no prototype at all - useful when you want a truly clean dictionary-style object with zero inherited behaviour.

ES6 introduced the class keyword, and it was a big deal for readability. Classes still use prototypes for instance inheritance, but they also add class-specific rules such as requiring new and keeping class methods non-enumerable.

Class syntax provides a higher-level way to define objects and inheritance while JavaScript still uses its prototype system. It is not identical to writing constructor functions by hand.

When you define a method inside a class, JavaScript puts it on the class's prototype automatically. You can verify this yourself - and it's a great thing to know for interviews.

javascript

// ---- Using a class (ES6 syntax) ----
class Animal {
  constructor(name) {
    this.name = name;
  }
  speak() {
    console.log(`${this.name} makes a noise.`);
  }
}

const cat = new Animal("Whiskers");
cat.speak(); // "Whiskers makes a noise."

// ---- Doing the exact same thing with prototypes directly ----
function AnimalProto(name) {
  this.name = name;
}
AnimalProto.prototype.speak = function () {
  console.log(`${this.name} makes a noise.`);
};

const cat2 = new AnimalProto("Whiskers");
cat2.speak(); // "Whiskers makes a noise."

// ---- Proof that class methods live on the prototype ----
console.log(typeof Animal);                     // "function" - classes are functions!
console.log(cat.speak === Animal.prototype.speak); // true

Both approaches produce the same result. The class syntax is much easier to read and write, especially as your code grows. But knowing the prototype mechanics behind it will make you a stronger developer - and will definitely come up in interviews.

Connection to classes: a class is a clearer authoring syntax for the same delegation system. Instance methods defined in a class are placed on ClassName.prototype, so instances can delegate method lookup there.

Have you ever wondered why every array can call .push(), or every string can call .toUpperCase()? It is because arrays and strings each have a built-in prototype packed full of shared methods.

In JavaScript, every array shares Array.prototype and every string shares String.prototype. Shared methods such as push() and toUpperCase() are defined on those prototypes.

You can even add your own methods to these built-in prototypes - though it is generally not recommended in production code, as it can break other code that shares the same prototype. It is fine in small experiments or learning exercises though.

javascript

const nums = [1, 2, 3];

// .push() lives on Array.prototype - not on nums directly
console.log(Object.prototype.hasOwnProperty.call(nums, "push")); // false
console.log("push" in nums);              // true (found on prototype)
console.log(Object.prototype.hasOwnProperty.call(Array.prototype, "push")); // true

// Same idea with strings
const greeting = "hello";
console.log(Object.prototype.hasOwnProperty.call(String.prototype, "toUpperCase")); // true
console.log(greeting.toUpperCase()); // "HELLO"

// The chain for an array goes:
// nums -> Array.prototype -> Object.prototype -> null
console.log(Object.getPrototypeOf(nums) === Array.prototype);           // true
console.log(Object.getPrototypeOf(Array.prototype) === Object.prototype); // true

// Adding a custom method to Array.prototype (for learning only!)
Array.prototype.sum = function () {
  return this.reduce((acc, val) => acc + val, 0);
};
console.log([1, 2, 3, 4].sum()); // 10

This is also why typeof [] returns "object" - arrays are objects, just with Array.prototype in their chain giving them all those extra powers.

This is one of the most confusing distinctions in JavaScript, and it trips up even experienced developers. Both relate to the prototype system, but they live in very different places.

[[Prototype]] is the object's internal prototype link. The standard way to read it is Object.getPrototypeOf(obj). __proto__ is a legacy accessor for that same relationship on many objects.

.prototype is the object JavaScript uses for instances created with regular constructor functions. When you use new, that object becomes the instance's [[Prototype]]. Class syntax builds on the same prototype-based idea underneath, which is why class methods live on the prototype too. Arrow functions are different: you cannot use them with new, and (() => {}).prototype is undefined.

  • Object.getPrototypeOf(instance) - the standard way to read the instance's [[Prototype]]
  • instance.__proto__ - a legacy accessor for that same relationship
  • Constructor.prototype - the object new instances inherit from when created with new

javascript

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

User.prototype.greet = function () {
  console.log("Hi, I am " + this.name);
};

const alice = new User("Alice");

// alice inherits from User.prototype
console.log(Object.getPrototypeOf(alice) === User.prototype); // true
console.log(alice.__proto__ === User.prototype);              // true (legacy accessor)

// User.prototype is NOT the prototype of the User function itself
// It is the template for instances, not for the function object
console.log(Object.getPrototypeOf(User) === Function.prototype); // true

alice.greet(); // "Hi, I am Alice" - found via the prototype chain

javascript

// Prefer Object.getPrototypeOf() over __proto__ in production code
// __proto__ is a legacy accessor for an object's [[Prototype]]

function Car(model) {
  this.model = model;
}

const myCar = new Car("Tesla");

// Both return the same result:
console.log(myCar.__proto__ === Car.prototype);              // true (old way)
console.log(Object.getPrototypeOf(myCar) === Car.prototype); // true (modern way)

// Summary of the full chain:
// myCar.__proto__          === Car.prototype
// Car.prototype.__proto__  === Object.prototype
// Object.prototype.__proto__ === null  (end of chain)

Before ES6 classes existed, the standard way to create many objects of the same "type" was a constructor function called with new.

When you call a function with new, JavaScript automatically does four things:

  1. Creates a brand new empty object.
  2. Sets the new object's internal [[Prototype]] link to Constructor.prototype.
  3. Runs the constructor with this bound to the new object.
  4. Returns the new object, unless the constructor explicitly returns another object. If it returns a primitive, that primitive is ignored.

javascript

function Person(name, age) {
  // 'this' is the brand new object created by 'new'
  this.name = name;
  this.age  = age;
}

// Methods go on the prototype - shared by all instances
Person.prototype.introduce = function () {
  console.log(`I am ${this.name}, ${this.age} years old.`);
};

const bob   = new Person("Bob", 28);
const carol = new Person("Carol", 34);

bob.introduce();   // "I am Bob, 28 years old."
carol.introduce(); // "I am Carol, 34 years old."

// Both instances share the same method via the prototype
console.log(bob.introduce === carol.introduce); // true

// Own properties are separate per instance
console.log(bob.name);   // "Bob"
console.log(carol.name); // "Carol"

javascript

// A simplified demonstration of how 'new' works
// It is helpful for learning, but it is not a perfect reimplementation

function Person(name) {
  this.name = name;
}
Person.prototype.greet = function () {
  console.log("Hello, " + this.name);
};

// Calling: const p = new Person("Alice")
// ... is roughly equivalent to:
function manualNew(Constructor, ...args) {
  const obj = Object.create(Constructor.prototype); // steps 1 & 2
  const result = Constructor.apply(obj, args);      // step 3
  return result !== null && (typeof result === "object" || typeof result === "function") ? result : obj; // step 4
}

const p = manualNew(Person, "Alice");
p.greet();                                            // "Hello, Alice"
console.log(Object.getPrototypeOf(p) === Person.prototype); // true

ES6 extends makes prototype-based inheritance much easier to read and write. When class Dog extends Animal, JavaScript automatically wires up the prototype chain so instances of Dog can reach methods from both Dog.prototype and Animal.prototype.

super() inside the constructor calls the parent class constructor. You must call super() before using this in a derived class - failing to do so throws a ReferenceError.

javascript

class Animal {
  constructor(name) {
    this.name = name;
  }
  speak() {
    console.log(`${this.name} makes a sound.`);
  }
}

class Dog extends Animal {
  constructor(name, breed) {
    super(name); // must call super() before using 'this'
    this.breed = breed;
  }
  speak() {
    // Override the parent method
    console.log(`${this.name} barks.`);
  }
  info() {
    console.log(`${this.name} is a ${this.breed}.`);
  }
}

const rex = new Dog("Rex", "Labrador");
rex.speak(); // "Rex barks."  (Dog's own override)
rex.info();  // "Rex is a Labrador."

// The prototype chain is intact
console.log(rex instanceof Dog);    // true
console.log(rex instanceof Animal); // true

// extends wires: Dog.prototype.__proto__ === Animal.prototype
console.log(Object.getPrototypeOf(Dog.prototype) === Animal.prototype); // true

javascript

// Calling the parent method alongside the child's override using super.method()

class Shape {
  constructor(color) {
    this.color = color;
  }
  describe() {
    return `A ${this.color} shape`;
  }
}

class Circle extends Shape {
  constructor(color, radius) {
    super(color);
    this.radius = radius;
  }
  describe() {
    // Call parent describe() and then extend it
    return super.describe() + ` (circle, radius ${this.radius})`;
  }
  area() {
    return Math.PI * this.radius ** 2;
  }
}

const c = new Circle("red", 5);
console.log(c.describe());        // "A red shape (circle, radius 5)"
console.log(c.area().toFixed(2)); // "78.54"

Prototype methods use this to reference the object used at the call site. The value of this is determined when the function is called, not where it was defined. Calling t.tick() gives the method the object t as this, but extracting the method into a standalone variable changes how this is determined.

javascript

function Timer(label) {
  this.label = label;
  this.count = 0;
}

Timer.prototype.tick = function () {
  this.count++;
  console.log(this.label + ": " + this.count);
};

const t = new Timer("Countdown");
t.tick(); // "Countdown: 1"

// Problem: extracting the method loses 'this'
const detached = t.tick;
// detached(); // TypeError or NaN - 'this' is no longer 't'

// Fix 1: bind() permanently ties 'this' to 't'
const boundTick = t.tick.bind(t);
boundTick(); // "Countdown: 2"

// Fix 2: always call through the object
t.tick();    // "Countdown: 3"

javascript

// Arrow functions in class constructors capture 'this' lexically
// This is a common pattern to avoid binding issues with callbacks

class Button {
  constructor(label) {
    this.label   = label;
    this.clicked = 0;
    // Arrow function: 'this' is always the Button instance
    this.handleClick = () => {
      this.clicked++;
      console.log(this.label + " clicked " + this.clicked + " time(s)");
    };
  }
}

const btn = new Button("Submit");

// Simulate passing the handler to an event listener
const handler = btn.handleClick;
handler(); // "Submit clicked 1 time(s)" - arrow kept 'this'
handler(); // "Submit clicked 2 time(s)"

Arrow functions do not have their own this. The arrow function assigned to handleClick captures the this value from the Button constructor, so it still refers to the instance when the handler is called separately. The trade-off is that each instance gets its own function instead of sharing one prototype method.

This is convenient, but each instance gets its own separate handleClick function. A method on Button.prototype would be shared by all instances instead.

Both check whether a property exists on an object, but they differ in where they look. In modern code, prefer Object.hasOwn(obj, key). If you need older runtime support, use Object.prototype.hasOwnProperty.call(obj, key).

  • Object.hasOwn(obj, 'key') - returns true only if the property is directly on the object itself (not inherited).
  • 'key' in obj - returns true if the property exists on the object or anywhere in the prototype chain.

javascript

// Modern own-property check:
// Object.hasOwn(obj, "key")
// Older-compatible fallback:
// Object.prototype.hasOwnProperty.call(obj, "key")
const hasOwn = typeof Object.hasOwn === "function"
  ? Object.hasOwn
  : (obj, key) => Object.prototype.hasOwnProperty.call(obj, key);

function Vehicle(make) {
  this.make = make;
}
Vehicle.prototype.drive = function () {
  console.log("Driving...");
};

const v = new Vehicle("Toyota");

// 'make' is an own property (set by the constructor)
console.log(hasOwn(v, "make"));  // true
console.log("make" in v);               // true

// 'drive' is inherited, so it is not an own property
console.log(hasOwn(v, "drive")); // false
console.log("drive" in v);              // true - found on the prototype

// 'toString' lives on Object.prototype, far up the chain
console.log(hasOwn(v, "toString")); // false
console.log("toString" in v);              // true

// Practical pattern: iterate own properties only
for (let key in v) {
  if (hasOwn(v, key)) {
    console.log("Own:", key); // Only logs "make"
  }
}

The modern alternative is Object.hasOwn(obj, key) (ES2022). It avoids edge cases with objects created via Object.create(null) which have no hasOwnProperty method at all. If you need older runtime support, use Object.prototype.hasOwnProperty.call(obj, "key").

javascript

// Object.hasOwn() - ES2022 modern replacement for hasOwnProperty()
const hasOwn = typeof Object.hasOwn === "function"
  ? Object.hasOwn
  : (obj, key) => Object.prototype.hasOwnProperty.call(obj, key);

const settings = { theme: "dark", lang: "en" };
console.log(hasOwn(settings, "theme"));   // true
console.log(hasOwn(settings, "toString")); // false

// Edge case: an object with NO prototype can't call .hasOwnProperty()
const bare = Object.create(null);
bare.key = "value";

// bare.hasOwnProperty("key") -> TypeError: bare.hasOwnProperty is not a function
// Object.hasOwn() works safely on any object:
console.log(hasOwn(bare, "key")); // true

Here are the key things to take away from this tutorial:

  • Most objects in JavaScript have an internal [[Prototype]] link to another object. Some, like objects created with Object.create(null), have null instead.
  • When JavaScript looks for a property, it walks up the prototype chain until it either finds it or reaches null.
  • Object.create(proto) lets you create a new object with a specific prototype, giving full control over inheritance.
  • class syntax provides a higher-level way to define objects and inheritance while JavaScript still uses its prototype system.
  • Built-in types like Array, String, and Object all have their own prototypes (Array.prototype, String.prototype, Object.prototype) where shared methods live.
  • Most prototype chains eventually reach Object.prototype and then null, but Object.create(null) creates an object with no prototype at all.
  • [[Prototype]] is the internal link on an object. Object.getPrototypeOf() reads it, and __proto__ is a legacy accessor for it. Constructor.prototype is a different thing.
  • Prefer Object.getPrototypeOf() over accessing __proto__ directly in production code.
  • When you call a function with new, JavaScript creates a new object, links it to Constructor.prototype, runs the constructor with this bound to the new object, and returns it unless the constructor returns another object. Primitive returns are ignored.
  • extends wires up the class prototype chain automatically. Always call super() in the subclass constructor before using this.
  • this inside a prototype method is determined at call time. Detaching a method from its object loses the binding. Arrow functions can avoid that, but when stored per instance they create a separate function for each instance instead of sharing one prototype method.
  • hasOwnProperty() checks only own properties; the in operator also checks inherited properties. Use Object.hasOwn() for a modern, safer own-property check.
  • Avoid modifying built-in prototypes in production code - it can cause unexpected behaviour across your entire codebase.

Reviewed by

SimplyJavaScript Editorial Team

Technical editors and JavaScript educators with hands-on experience building frontend projects, writing learning material, and reviewing tutorials for clarity, accuracy, and beginner-friendly guidance.