JavaScript Prototypes and Inheritance: Tutorial with Runnable Examples

Understand how JavaScript finds inherited properties, shares methods, and implements constructor and class inheritance. Trace the chains, fix common mistakes, and test yourself.

KnowledgeGate Team

Exam prep & CS education

Updated 1 Sep 20265 min read

JavaScript makes three ideas look more mysterious than they are: an object's hidden prototype link, a constructor function's public prototype property, and class with extends. JavaScript inheritance is property lookup through linked objects, and class syntax still uses that machinery. In a browser console or Node.js, trace a prototype chain, diagnose common failures, and practise with exercises.

Start with the prototype chain, not class syntax

Every ordinary object has an internal [[Prototype]] that refers to another object or null. For a property read, JavaScript checks the receiver, follows [[Prototype]], and repeats. Reaching null without a match produces undefined.

This internal link differs from a constructor function's public .prototype object. Inspect it with Object.getPrototypeOf, and create one with Object.create. Reserve Object.setPrototypeOf for labelled demonstrations. You may encounter the legacy __proto__ accessor.

const animal = { legs: 4, sound: "generic" };
const dog = Object.create(animal);
dog.name = "Milo";
dog.sound = "bark";

dog.name === "Milo";
dog.legs === 4;
dog.sound === "bark";
Object.hasOwn(dog, "legs") === false;
Object.getPrototypeOf(dog) === animal;

delete dog.sound;
dog.sound === "generic";

dog.sound initially shadows animal.sound. Deleting it removes only the own property, so the next lookup finds "generic" on animal. For broader study, explore the Coding & Skill Development Courses.

Diagram of the dog-to-animal prototype chain showing how JavaScript resolves dog.name, dog.legs, and the shadowed dog.sound lookup.

See what constructor functions do with .prototype

A constructor function is a function intended for use with new. Conceptually, new Dog("Rex", "Indie") creates an object, sets its [[Prototype]] to Dog.prototype, calls Dog with this bound to the object, and returns that object unless the constructor explicitly returns another object. .prototype belongs to constructor functions. An instance such as rex does not normally have its own .prototype property.

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

Animal.prototype.describe = function () {
  return `${this.name} is an animal`;
};

const a1 = new Animal("Asha");
const a2 = new Animal("Bunty");

a1.describe() === "Asha is an animal";
a2.describe() === "Bunty is an animal";
a1.describe === a2.describe;
Object.hasOwn(a1, "describe") === false;

Both instances reuse one function on Animal.prototype; lookup does not copy it. Their name values remain own properties. Keep per-instance state in the constructor and shared methods on the prototype.

Build inheritance by linking Dog.prototype to Animal.prototype

Dog inherits Animal's state and methods through a prototype link. The order matters because replacing Dog.prototype discards anything previously attached to that object.

function Dog(name, breed) {
  Animal.call(this, name);
  this.breed = breed;
}

Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;
Dog.prototype.speak = function () {
  return `${this.name} barks`;
};

const rex = new Dog("Rex", "Indie");

rex.name === "Rex";
rex.breed === "Indie";
rex.speak() === "Rex barks";
rex.describe() === "Rex is an animal";
rex instanceof Dog === true;
rex instanceof Animal === true;
Object.getPrototypeOf(rex) === Dog.prototype;
Object.getPrototypeOf(Dog.prototype) === Animal.prototype;

The two key lines do separate jobs. Animal.call(this, name) initialises the base object's own state on rex. Object.create(Animal.prototype) links method lookup from Dog.prototype to Animal.prototype. Neither substitutes for the other.

Replacing the default prototype also changes its conventional constructor reference, so Dog.prototype.constructor = Dog repairs that convention. Do not treat constructor as a secure type check. Here, instanceof is the useful chain check: it asks whether Dog.prototype or Animal.prototype occurs in rex's prototype chain.

Object graph after new Dog('Rex','Indie') linking rex to Dog.prototype to Animal.prototype, with the speak and describe lookup paths.

Translate the same model into class and extends

Run this version in a fresh console session or a separate file because it reuses the names Animal, Dog, and rex.

class Animal {
  constructor(name) {
    this.name = name;
  }

  describe() {
    return `${this.name} is an animal`;
  }
}

class Dog extends Animal {
  constructor(name, breed) {
    super(name);
    this.breed = breed;
  }

  speak() {
    return `${this.name} barks`;
  }
}

const rex = new Dog("Rex", "Indie");

rex.name === "Rex";
rex.breed === "Indie";
rex.speak() === "Rex barks";
rex.describe() === "Rex is an animal";
Object.getPrototypeOf(rex) === Dog.prototype;
Object.getPrototypeOf(Dog.prototype) === Animal.prototype;

super(name) calls the base constructor and must run before a derived constructor uses this. Class syntax does not remove prototypes. It makes the intended relationship easier to read. Prefer class in application code, but retain the chain model for debugging and interviews.

Concern

Constructor syntax

Class syntax

Instance state

this.name

this.name

Shared method

Animal.prototype.describe

describe()

Inheritance link

Object.create(Animal.prototype)

extends Animal

Base initialisation

Animal.call(this, name)

super(name)

Avoid four prototype mistakes that produce misleading code

Mistake

Symptom

Correction

Add speak, then replace Dog.prototype

A later rex.speak is undefined

Link first, then add child methods

Omit Animal.call(this, name) or super(name)

rex.name is undefined

Initialise the base with "Rex"

Put Animal.prototype.tags = []

After a1.tags.push("friendly"), a2.tags also contains "friendly"

Put this.tags = [] inside Animal, making a1.tags !== a2.tags

Treat inherited as own

"describe" in rex is true, but Object.hasOwn(rex, "describe") is false

Choose the check that matches the question

The first error comes from code such as Dog.prototype.speak = ...; Dog.prototype = Object.create(Animal.prototype);. The second assignment replaces the object that held speak. Runtime prototype changes can also complicate reasoning and performance. Build the intended chain when creating objects with class, new, or Object.create.

Practise the output questions interviews and assessments use

Assessments and interviews can test lookup order, shadowing, own versus inherited properties, constructor setup, and instanceof reasoning. Trace each read and write instead of guessing. If those foundations feel uncertain, review JavaScript Basics: Types, Functions, Arrays and Async before attempting the output trace below.

const base = { score: 10 };
const child = Object.create(base);
child.score += 5;
console.log(base.score, child.score); // 10 15

The right-hand read finds inherited 10. Adding 5 produces 15, and assignment creates the child's own score, so the base stays 10. Therefore Object.hasOwn(child, "score") === true. After delete child.score, lookup reaches the base again, so child.score === 10 and Object.hasOwn(child, "score") === false.

From the shared model, Object.hasOwn(rex, "speak") is false because lookup finds speak on Dog.prototype. Object.hasOwn(Dog.prototype, "speak") is true because that object stores the method. "describe" in rex is true because in searches the full chain and finds it on Animal.prototype.

Solve two exercises, then choose the next JavaScript step

First, run this code and predict four results before checking them:

const vehicle = {
  wheels: 4,
  start() { return "started"; }
};
const bike = Object.create(vehicle);
bike.wheels = 2;

For bike.wheels, vehicle.wheels, bike.start(), and bike.wheels after delete bike.wheels, the answers are 2, 4, "started", and 4.

Second, reproduce the constructor chain with const luna = new Dog("Luna", "Indie"). Check that luna.describe() gives "Luna is an animal", luna.speak() gives "Luna barks", and both instanceof checks are true. The solution needs Animal.call(this, name) for base state and Dog.prototype = Object.create(Animal.prototype) for lookup.

The short version

Instances hold changing state, prototypes hold shared behaviour, inheritance is a lookup chain, and class is clearer syntax over that model. Build the full foundation with the Complete JavaScript course. Move to the React and Redux course after your JavaScript fundamentals are firm.