Advanced2 questionsFull Guide

JavaScript Prototype & Prototypal Inheritance Interview Questions

Prototypal inheritance is JavaScript's core object system. Learn prototype chains, Object.create, and how class syntax maps to prototypes.

The Mental Model

Picture a company employee handbook. Every department has its own handbook, but instead of duplicating the company-wide rules in each department's version, the department handbook just says "for anything not in here, see the company handbook." If the company handbook doesn't have it either, it says "see the legal document on file with HR." Each handbook delegates up a chain rather than repeating what's already written above. That chain is the prototype chain. Every JavaScript object has a hidden link to another object — its prototype. When you access a property the object doesn't have directly, JavaScript follows the link to the prototype and looks there. If it's not there either, it follows that object's link, and so on, until it either finds the property or reaches the end of the chain (null) and returns undefined. The key insight: prototypes are not a way to copy behavior — they're a live delegation chain. Objects don't get private copies of their prototype's methods. They share them. When you call array.push(), there's no push method on your specific array — it's on Array.prototype, and your array delegates there at call time. This is how JavaScript achieves inheritance without classes — though classes are now available as syntax on top of exactly this same system.

The Explanation

Every object has a prototype

In JavaScript, almost every object has an internal slot called [[Prototype]] — a link to another object. Property lookup follows this chain automatically and silently.

const obj = { name: 'Alice' }

// obj's [[Prototype]] is Object.prototype
// You can access it via:
Object.getPrototypeOf(obj)  // Object.prototype — the right way
obj.__proto__               // Object.prototype — works but not recommended

// Object.prototype's [[Prototype]] is null — end of the chain
Object.getPrototypeOf(Object.prototype)  // null

// Property lookup in action:
console.log(obj.name)         // 'Alice' — found on obj directly
console.log(obj.toString())   // works — not on obj, found on Object.prototype
console.log(obj.missing)      // undefined — not found anywhere in the chain

The prototype chain — property lookup step by step

const animal = {
  breathes: true,
  describe() { return `I breathe: ${this.breathes}` }
}

const dog = {
  name: 'Rex',
  bark() { return 'Woof!' }
}

// Set dog's prototype to animal
Object.setPrototypeOf(dog, animal)

// Now the chain is: dog → animal → Object.prototype → null

dog.name       // 'Rex'     — found on dog
dog.bark()     // 'Woof!'   — found on dog
dog.breathes   // true      — NOT on dog, found on animal (prototype)
dog.describe() // 'I breathe: true' — found on animal, but THIS = dog
               // 'this' in prototype methods refers to the calling object, not the prototype

dog.missing    // undefined — not on dog, not on animal, not on Object.prototype
dog.hasOwnProperty('name')      // true  — 'name' is OWN property
dog.hasOwnProperty('breathes')  // false — 'breathes' is inherited

Constructor functions and prototype

Before ES6 classes, constructor functions were the standard way to create objects with shared methods. Understanding them reveals exactly what classes do under the hood.

// Constructor function — called with 'new'
function Person(name, age) {
  // 'this' is the newly created object
  this.name = name   // OWN property — stored on each instance
  this.age  = age    // OWN property — stored on each instance
  // Don't do this — creates a new function for each instance:
  // this.greet = function() { ... }
}

// Methods go on the PROTOTYPE — shared by all instances
Person.prototype.greet = function() {
  return `Hi, I'm ${this.name}, age ${this.age}`
}

Person.prototype.birthday = function() {
  this.age++
}

const alice = new Person('Alice', 30)
const bob   = new Person('Bob', 25)

alice.greet()  // "Hi, I'm Alice, age 30"
bob.greet()    // "Hi, I'm Bob, age 25"

// Both instances share ONE greet function on Person.prototype — not copies
alice.greet === bob.greet  // true — same function reference

// The prototype chain for alice:
// alice → Person.prototype → Object.prototype → null

What 'new' actually does — four steps

// When you call new Person('Alice', 30), JavaScript:
// 1. Creates a new empty object: {}
// 2. Sets its [[Prototype]] to Person.prototype
// 3. Calls Person with 'this' set to the new object
// 4. Returns the new object (unless Person explicitly returns a different object)

// Manual implementation of new:
function myNew(Constructor, ...args) {
  const instance = Object.create(Constructor.prototype)  // steps 1 & 2
  const result   = Constructor.apply(instance, args)     // step 3
  return typeof result === 'object' && result !== null   // step 4
    ? result
    : instance
}

const alice = myNew(Person, 'Alice', 30)
alice.greet()  // "Hi, I'm Alice, age 30" — works identically

Object.create() — prototypal inheritance directly

// Object.create(proto) creates a new object with proto as its [[Prototype]]
const animal = {
  type: 'Animal',
  describe() { return `I am a ${this.type} named ${this.name}` }
}

const dog = Object.create(animal)
dog.type = 'Dog'
dog.name = 'Rex'
dog.bark = function() { return 'Woof!' }

dog.describe()   // 'I am a Dog named Rex' — uses animal's method, dog's data
dog.bark()       // 'Woof!'

// Object.create(null) — object with NO prototype
const pure = Object.create(null)
pure.key = 'value'
pure.toString  // undefined — no Object.prototype in chain
// Safe as a dictionary — no inherited properties that could clash with data keys

ES6 Classes — syntax sugar over prototypes

Classes don't add a new object model. They're a cleaner syntax for the same constructor + prototype pattern.

// ES6 class
class Animal {
  constructor(name, sound) {
    this.name  = name   // own property on each instance
    this.sound = sound  // own property on each instance
  }

  speak() {                       // goes on Animal.prototype
    return `${this.name} says ${this.sound}`
  }

  static create(name, sound) {    // static — on Animal itself, not Animal.prototype
    return new Animal(name, sound)
  }
}

class Dog extends Animal {
  constructor(name) {
    super(name, 'Woof')  // calls Animal's constructor
    this.tricks = []     // own property
  }

  learn(trick) {          // goes on Dog.prototype
    this.tricks.push(trick)
  }

  speak() {               // OVERRIDES Animal.prototype.speak on Dog.prototype
    return `${super.speak()} and knows ${this.tricks.length} tricks`
  }
}

const rex = new Dog('Rex')
rex.learn('sit')
rex.speak()  // 'Rex says Woof and knows 1 tricks'

// Prototype chain for rex:
// rex → Dog.prototype → Animal.prototype → Object.prototype → null

// Exactly equivalent to the pre-class version:
Object.getPrototypeOf(rex)              // Dog.prototype
Object.getPrototypeOf(Dog.prototype)    // Animal.prototype
Object.getPrototypeOf(Animal.prototype) // Object.prototype

hasOwnProperty vs 'in' operator

const parent = { inherited: true }
const child  = Object.create(parent)
child.own    = true

'own'       in child  // true  — checks entire chain
'inherited' in child  // true  — found on parent via chain
'missing'   in child  // false — not anywhere

child.hasOwnProperty('own')       // true  — directly on child
child.hasOwnProperty('inherited') // false — on parent, not child

// for...in iterates ALL enumerable properties including inherited ones
for (const key in child) {
  console.log(key)  // 'own', 'inherited'
}

// Object.keys — own enumerable only
Object.keys(child)  // ['own'] — ignores inherited

Prototype pollution — a real security concern

// Object.prototype is shared by ALL objects
// Modifying it affects every object in your program — and every library

// ❌ Never do this:
Object.prototype.isAdmin = true

const user = {}
user.isAdmin  // true — now every object has isAdmin!

// This is prototype pollution — a real attack vector in security exploits
// User-controlled input like { '__proto__': { 'isAdmin': true } }
// when merged with a target object can inject properties on Object.prototype

// Safe merge patterns:
function safeMerge(target, source) {
  for (const key of Object.keys(source)) {
    if (key !== '__proto__' && key !== 'constructor' && key !== 'prototype') {
      target[key] = source[key]
    }
  }
  return target
}

// Or use Object.assign on a null-prototype object for config parsing:
const config = Object.assign(Object.create(null), userInput)

Performance — prototype chain depth matters

// Property lookup traverses the chain on every access
// Deeply nested prototype chains are slower (though JS engines optimize heavily)

// Shallow is faster — keep inheritance chains short (3-4 levels max)
// Own properties are fastest — direct access, no chain traversal

// V8 optimization: hidden classes
// V8 creates a hidden class for each unique property layout
// Adding properties in a consistent order allows V8 to optimize property access
// Breaking the pattern (adding props in random order or deleting them) degrades performance

// ✓ Consistent property layout:
function Point(x, y) {
  this.x = x  // always added in this order
  this.y = y  // V8 can optimize
}

// ❌ Inconsistent layout:
const p1 = {}; p1.x = 1; p1.y = 2
const p2 = {}; p2.y = 1; p2.x = 2  // different order — different hidden class

Common Misconceptions

⚠️

Many devs think ES6 classes create a fundamentally different object model than prototypes — but actually classes are syntactic sugar over constructor functions and prototype chains. The runtime behavior is identical. class Foo {} creates a function Foo, and all methods go on Foo.prototype, linked to every instance via [[Prototype]], exactly as they did before classes existed.

⚠️

Many devs think prototype methods are copied onto each new instance — but actually instances never get copies of prototype methods. They share a single copy that lives on the prototype object. This is the memory efficiency of prototypal inheritance — 1000 Array instances share one Array.prototype.push, not 1000 separate push functions.

⚠️

Many devs think __proto__ is the right way to access the prototype — but actually __proto__ is a deprecated accessor that works in browsers for historical reasons but is not part of the core specification. The correct API is Object.getPrototypeOf(obj) to read and Object.setPrototypeOf(obj, proto) to set. __proto__ works but signals unfamiliarity with modern JavaScript.

⚠️

Many devs think Object.create(null) creates an object with no useful properties — but actually it creates an object with no prototype at all, making it a pure dictionary with zero risk of property name collisions with inherited properties like toString, hasOwnProperty, or valueOf. This is the correct tool for safe key-value stores built from user input.

⚠️

Many devs think instanceof checks if an object is a direct instance of a class — but actually instanceof walks the entire prototype chain. new Dog() instanceof Animal is true if Animal.prototype appears anywhere in Dog's prototype chain. It does not check if the object was directly constructed by that constructor — only if the prototype is in the chain.

⚠️

Many devs think modifying a prototype is always harmless — but actually prototype pollution (adding or modifying properties on Object.prototype or other shared prototypes) affects every object in the application and every library. It is a real security vulnerability class with CVEs, not just a style concern, and user input that merges into objects without sanitization is the most common attack vector.

Where You'll See This in Real Code

All built-in JavaScript methods exist on prototypes — Array.prototype has push, pop, map, filter, reduce. String.prototype has split, trim, includes. You never construct these methods yourself, yet every array and string you create has access to them through the prototype chain. The language itself is built on prototype delegation.

Polyfills work by adding methods to built-in prototypes — if older browsers don't have Array.prototype.includes, a polyfill adds it: if (!Array.prototype.includes) { Array.prototype.includes = function() { ... } }. This is prototype extension used correctly (on non-existent methods, in controlled environments). It's the exact same mechanism that makes prototype pollution dangerous when done with user input.

React's class components are JavaScript classes (prototype-based) that extend React.Component — meaning every class component instance gets React's lifecycle methods (componentDidMount, render, setState) through prototype chain delegation. Function components with hooks replaced this pattern entirely because prototype chains added cognitive overhead that hooks eliminated.

Lodash's chain() method temporarily modifies an object's prototype to enable method chaining — _.chain(array).filter().map().value() works because chain() creates a wrapper object whose prototype has all Lodash methods. This is prototype-based API design where the prototype is set programmatically for a specific use case.

Node.js's EventEmitter is pure prototype-based inheritance — EventEmitter.prototype.on, EventEmitter.prototype.emit, EventEmitter.prototype.removeListener are all defined on the prototype. When you extend EventEmitter in Node.js (class MyEmitter extends EventEmitter), your instances get all event handling through prototype delegation, exactly as prototypes were designed to work.

TypeScript's compiled class output in older targets (ES5) is a manual recreation of the prototype pattern — the TypeScript compiler generates a constructor function with methods on the prototype, a __extends helper that wires up the prototype chain, and calls to the parent constructor via call(). Reading TypeScript's compiled output is the best crash course in what ES6 classes actually do.

Interview Cheat Sheet

  • [[Prototype]]: every object's hidden link to its parent — the chain ends at null
  • Property lookup: own properties first → [[Prototype]] → up the chain → undefined
  • Object.getPrototypeOf(obj): correct way to read [[Prototype]] (not __proto__)
  • Constructor functions: methods on Constructor.prototype are shared by all instances
  • new keyword: creates object, sets [[Prototype]] to Constructor.prototype, runs constructor, returns object
  • Object.create(proto): creates new object with proto as [[Prototype]]
  • Classes: syntactic sugar — same prototype chain under the hood
  • hasOwnProperty: only own, not inherited; 'in' operator: own AND inherited
  • instanceof: checks if Constructor.prototype exists anywhere in the chain
  • Prototype pollution: modifying Object.prototype affects ALL objects — security risk
💡

How to Answer in an Interview

  • 1.Implement new manually — it reveals you understand all four steps and the prototype link
  • 2.Diagram the chain: instance → Constructor.prototype → Object.prototype → null — draw it every time
  • 3."Classes are sugar over prototypes" then prove it — typeof class === 'function', class methods go on .prototype
  • 4.The difference between own properties and prototype properties (hasOwnProperty) is asked constantly
  • 5.Prototype pollution as a security concern elevates your answer from junior to senior level
  • 6.Connecting to how built-in methods (Array.prototype.push) work shows you see prototypes as fundamental, not exotic
📖 Deep Dive Articles
Arrow Functions vs Regular Functions in JavaScript: 6 Key Differences9 min readTop 50 JavaScript Interview Questions (With Deep Answers)18 min readJavaScript Error Handling: A Complete Guide to Errors, try/catch, and Async Failures11 min readJavaScript Prototypes Explained: The Object Model Behind Every Class and Method10 min readJavaScript `this` Keyword Explained: Five Rules, Zero Guessing10 min readJavaScript Output Questions: 30 Tricky Examples That Test Real Understanding14 min read

Practice Questions

2 questions
#01

How does prototypal inheritance work in JavaScript?

🟢 EasyObjects PRO💡 Every object has a [[Prototype]] link — property lookup walks the chain
#02

Prototype pollution vulnerability

🔴 HardFix the CodeDebug

Related Topics

JavaScript "this" Keyword Interview Questions
Intermediate·6–10 Qs
JavaScript Class Interview Questions
Intermediate·5–8 Qs
JavaScript Object Interview Questions
Intermediate·8–12 Qs
🎯

Can you answer these under pressure?

Reading answers is not the same as knowing them. Practice saying them out loud with AI feedback — that's what builds real interview confidence.

Practice Free →Try Output Quiz