DevPrep
  • Interview Prep
  • Projects
  • Resources
  • Pricing
  • About Us
Submit Question
DevPrep
  • Pricing
  • About Us
Submit Question
  1. Home
  2. Articles
  3. Frontend Engineering
  4. Prototypal Inheritance in JavaScript — How It Actually Works
XLinkedInReddit
MediumFrontend Engineering

Prototypal Inheritance in JavaScript — How It Actually Works

D
DevPrep Team
February 9, 2026·4 min read·0
Table of Contents
  • What is Prototypal Inheritance?
  • The Prototype Chain — Visualized
  • __proto__ vs prototype vs Object.getPrototypeOf()
  • ES6 Classes — It's Still Prototypes Underneath
  • Real Production Use Cases
  • 1. Polyfills
  • 2. Object.create(null) — Production Pattern at Google
  • 3. Mixin Pattern
  • Production Issues I've Encountered
  • Issue 1: Prototype Pollution Attack
  • Issue 2: hasOwnProperty Confusion
  • Best Practices
  • Interview Tip

Written by Rahul · Frontend Engineer at Google · Updated 2025

Here's the honest truth — I've interviewed over 200 frontend candidates, and maybe 10% actually understand prototypal inheritance. Most can say "objects inherit from other objects," but when you ask them to explain the prototype chain or why Object.create(null) is useful, they freeze. Let me break this down the way I wish someone had explained it to me.

What is Prototypal Inheritance?

In JavaScript, there are no traditional "classes" (even ES6 classes are syntactic sugar). Instead, objects can inherit directly from other objects through a hidden link called [[Prototype]].

const animal = {
  eat() { console.log("Eating..."); },
  sleep() { console.log("Sleeping..."); }
};

const dog = Object.create(animal);
dog.bark = function() { console.log("Woof!"); };

dog.bark();  // "Woof!" — own property
dog.eat();   // "Eating..." — inherited from animal
dog.sleep(); // "Sleeping..." — inherited from animal

When you access dog.eat(), JavaScript first looks at dog itself. Not found? It follows the [[Prototype]] link to animal. Found it! This is the prototype chain.

The Prototype Chain — Visualized

dog → animal → Object.prototype → null

dog.bark()       → found on dog ✅
dog.eat()        → not on dog → found on animal ✅
dog.toString()   → not on dog → not on animal → found on Object.prototype ✅
dog.fly()        → not on dog → not on animal → not on Object.prototype → undefined ❌

__proto__ vs prototype vs Object.getPrototypeOf()

This is where most people get confused. Let me clear it up:

TermWhat It IsUse It?
__proto__The actual link to the parent object. Exists on every object.❌ Never in production. It's deprecated.
prototypeA property on functions only. Objects created with new Foo() get their [[Prototype]] set to Foo.prototype.✅ For constructor patterns
Object.getPrototypeOf(obj)The correct way to read an object's prototype.✅ Always use this
function Person(name) {
  this.name = name;
}
Person.prototype.greet = function() {
  return `Hi, I am ${this.name}`;
};

const rahul = new Person("Rahul");

// These are the SAME object:
console.log(rahul.__proto__ === Person.prototype); // true
console.log(Object.getPrototypeOf(rahul) === Person.prototype); // true

ES6 Classes — It's Still Prototypes Underneath

class Animal {
  eat() { console.log("Eating"); }
}

class Dog extends Animal {
  bark() { console.log("Woof"); }
}

const buddy = new Dog();

// Under the hood, this is identical to:
// Dog.prototype.__proto__ === Animal.prototype
console.log(Object.getPrototypeOf(Dog.prototype) === Animal.prototype); // true

Classes don't add a new inheritance model. They're syntactic sugar that makes the prototype pattern look like classical inheritance.

Real Production Use Cases

1. Polyfills

Every polyfill you've ever used works through prototypal inheritance:

// Adding Array.prototype.at() for older browsers
if (!Array.prototype.at) {
  Array.prototype.at = function(index) {
    if (index < 0) index = this.length + index;
    return this[index];
  };
}

// Now ALL arrays have .at()
[1, 2, 3].at(-1); // 3

2. Object.create(null) — Production Pattern at Google

We use Object.create(null) to create truly empty objects for hash maps:

// Regular object has inherited properties
const cache = {};
console.log("toString" in cache); // true — inherited from Object.prototype!

// Clean object has nothing
const cleanCache = Object.create(null);
console.log("toString" in cleanCache); // false — truly empty

// Why this matters: prevents prototype pollution attacks
// and avoids key collisions with inherited properties

3. Mixin Pattern

const Serializable = {
  toJSON() {
    return JSON.stringify(this);
  },
  fromJSON(json) {
    return Object.assign(Object.create(this), JSON.parse(json));
  }
};

const Validatable = {
  validate() {
    return Object.keys(this).every(key => this[key] !== null);
  }
};

// Compose behaviors
const User = Object.assign(
  Object.create(null),
  Serializable,
  Validatable,
  {
    create(name, email) {
      const user = Object.create(this);
      user.name = name;
      user.email = email;
      return user;
    }
  }
);

Production Issues I've Encountered

Issue 1: Prototype Pollution Attack

A developer accepted user input and used it as object keys without sanitization:

// ❌ DANGEROUS — Prototype pollution
function merge(target, source) {
  for (let key in source) {
    target[key] = source[key];
  }
}

// Attacker sends: {"__proto__": {"isAdmin": true}}
merge({}, JSON.parse(userInput));
// Now ALL objects have isAdmin === true!

// ✅ FIX — Check for prototype keys
function safeMerge(target, source) {
  for (let key of Object.keys(source)) {
    if (key === "__proto__" || key === "constructor" || key === "prototype") continue;
    target[key] = source[key];
  }
}

Issue 2: hasOwnProperty Confusion

const config = getRemoteConfig();

// ❌ This checks the ENTIRE prototype chain
if ("debug" in config) { ... }

// ✅ This only checks the object itself
if (Object.hasOwn(config, "debug")) { ... }  // ES2022
// or
if (config.hasOwnProperty("debug")) { ... }

Best Practices

  1. Prefer ES6 classes for readability, but understand prototypes underneath
  2. Use Object.create(null) for dictionary/map objects to avoid prototype pollution
  3. Never modify built-in prototypes (except for polyfills in controlled environments)
  4. Use Object.hasOwn() instead of in operator when checking own properties
  5. Freeze prototypes of sensitive objects: Object.freeze(Object.prototype) in security-critical apps

Interview Tip

When asked "explain prototypal inheritance," draw the chain. Literally draw: instance → Constructor.prototype → Object.prototype → null. Then explain that property lookup walks this chain. Mention that ES6 classes are sugar. Mention Object.create(null). That's a senior-level answer.

Related Articles

MediumFrontend Engineering

System Design #12: Design a Multi-Step Form Wizard

7 min read
MediumFrontend Engineering

Mastering Senior-Level JavaScript Interview Concepts

2 min read
MediumFrontend Engineering

System Design #9: Design a Collaborative Text Editor

9 min read

Comments (0)

Sign in to leave a comment.

No comments yet. Be the first to comment.

Table of Contents

  • What is Prototypal Inheritance?
  • The Prototype Chain — Visualized
  • __proto__ vs prototype vs Object.getPrototypeOf()
  • ES6 Classes — It's Still Prototypes Underneath
  • Real Production Use Cases
  • 1. Polyfills
  • 2. Object.create(null) — Production Pattern at Google
  • 3. Mixin Pattern
  • Production Issues I've Encountered
  • Issue 1: Prototype Pollution Attack
  • Issue 2: hasOwnProperty Confusion
  • Best Practices
  • Interview Tip

Series

View all Frontend Engineering articles →

Practice

  • JavaScript
  • DSA
  • Machine Coding
  • System Design

Resources

  • Learning Tracks
  • Articles
  • Roadmaps
  • Compare Concepts
  • Glossary
  • Developer Tools
  • All Questions

Company

  • About
  • Pricing

Legal

  • Privacy Policy
  • Terms of Service
DevPrep

© 2026 DevPrep. All rights reserved.