Prototypal Inheritance Interview Questions & Answers

26 questions Updated 2026-06-18

JavaScript prototypal inheritance interview questions — Object.create, delegation, constructor inheritance, classical vs prototypal, and sharing behavior between objects.

Read the in-depth guidePrototypal Inheritance in JavaScript — The Complete Practical Guide

A model where objects inherit directly from other objects (their prototype), rather than from classes. A missing property is looked up on the prototype chain, so one object can reuse another's properties and methods.

const animal = { eats: true, walk() { return 'walking' } }
const rabbit = Object.create(animal)
rabbit.jumps = true
rabbit.walk()  // 'walking' — inherited
rabbit.eats    // true — inherited

JavaScript's inheritance is fundamentally object-to-object delegation. Even class is sugar over this prototypal mechanism.

Object.create(proto) makes a new object whose prototype is proto, giving you direct prototypal inheritance with no constructors.

const base = {
  init(name) { this.name = name; return this },
  describe() { return `I am ${this.name}` },
}
const child = Object.create(base)
child.shout = function () { return this.describe().toUpperCase() }

const c = Object.create(child).init('Ada')
c.shout()  // 'I AM ADA'

Each level delegates to the one above. This is the most direct expression of prototypal inheritance — no new, no prototype juggling.

Call the parent constructor for the instance fields, and link the prototypes so methods are inherited.

function Animal(name) { this.name = name }
Animal.prototype.eat = function () { return `${this.name} eats` }

function Dog(name) {
  Animal.call(this, name)               // inherit instance properties
}
Dog.prototype = Object.create(Animal.prototype) // inherit methods
Dog.prototype.constructor = Dog                 // restore constructor
Dog.prototype.bark = function () { return 'woof' }

This is the pre-ES6 pattern: Parent.call(this) for fields, Object.create(Parent.prototype) for the method chain, and reset constructor. class extends does all this for you.

  • Classical (Java/C++): classes are blueprints; objects are instances that copy structure from the class hierarchy.
  • Prototypal (JavaScript): objects delegate to live prototype objects; there's no copying, and the link is dynamic.
// prototypal: changing the prototype affects existing objects live
const proto = { greet() { return 'hi' } }
const obj = Object.create(proto)
proto.greet = () => 'hello'
obj.greet()  // 'hello' — live delegation, not a copy

JavaScript's class looks classical but is prototypal underneath. The key difference is delegation (live links) vs copying (static blueprints).

Reference the parent prototype's method and invoke it with the right this via call.

Dog.prototype.eat = function () {
  const base = Animal.prototype.eat.call(this)  // call parent, keep `this`
  return base + ' noisily'
}

With class, super.method() does this cleanly. Without classes, you must explicitly grab Parent.prototype.method and call it with the instance, or the parent method would run with the wrong this.

Define a method with the same name lower in the chain (on the child); it shadows the parent's version because lookup stops at the first match.

const animal = { speak() { return '...' } }
const dog = Object.create(animal)
dog.speak = function () { return 'Woof' }  // overrides
dog.speak()                                 // 'Woof'
Object.getPrototypeOf(dog).speak()          // '...' (parent still reachable)

The override is just a closer own/prototype property. The parent method remains accessible via the prototype if you need to call it (the manual super).

A factory function returns a new object without new or classes — often using closures for state and composition (rather than prototype chains) for behavior.

function createUser(name) {
  return {
    name,
    greet() { return `Hi, ${name}` },
  }
}
const u = createUser('Ada')

Factories favor composition over inheritance: instead of inheriting from a prototype, you assemble objects from pieces. Trade-off: methods aren't shared on a prototype (each object gets its own), costing some memory for many instances.

A way to share behavior across unrelated objects/classes by copying methods in, instead of inheriting — JavaScript's answer to the lack of multiple inheritance.

const serializable = { serialize() { return JSON.stringify(this) } }
const comparable = { equals(o) { return this.id === o.id } }

class Model {}
Object.assign(Model.prototype, serializable, comparable)

Mixins let a class gain capabilities from several sources. They're a flexible alternative to deep inheritance — compose the behaviors a type needs rather than forcing a single hierarchy.

Not in the classic multiple-inheritance sense, because each object has a single prototype chain — there's no ambiguity over which parent a method comes from. But mixins can reintroduce conflicts when two mixins define the same method.

const a = { run() { return 'a' } }
const b = { run() { return 'b' } }
Object.assign(target, a, b)
target.run()  // 'b' — last one wins, silently

With Object.assign, the last source wins on key collisions (no error). So single-prototype inheritance avoids diamonds, but you must manage mixin method clashes yourself.

No — only methods (and true constants) belong on the prototype. Mutable shared state on a prototype is accidentally shared by all instances.

function Cart() {}
Cart.prototype.items = []        // ALL carts share one array
const a = new Cart(), b = new Cart()
a.items.push('x')
b.items                          // ['x'] — leaked!

function Cart2() { this.items = [] }  // per-instance state in the constructor

Put per-instance data on this in the constructor; put shared behavior on the prototype. (This mirrors the Java "mutable static field" trap.)

Constructor functions have their own prototype chain (Child.__proto__ === Parent when using class extends), so "static" properties on the parent constructor are inherited by the child constructor.

class Parent { static create() { return new this() } }
class Child extends Parent {}
Child.create()   // works — static method inherited; `this` is Child

extends links both chains: instance methods via Child.prototype -> Parent.prototype, and statics via Child -> Parent. With plain constructor functions you'd wire statics up manually.

A property descriptors object — it lets you define own properties (with writable/enumerable/configurable flags) at the same time as setting the prototype.

const obj = Object.create(proto, {
  id: { value: 1, enumerable: true },
  secret: { value: 42, enumerable: false },
})

It's the same descriptor format as Object.defineProperties. Rarely needed, but it's how you create an object with both a chosen prototype and precisely configured own properties in one call.

An older pattern where a factory creates a base object, then augments it with extra properties/methods before returning it — inheritance without prototypes.

function createAnimal(name) {
  const obj = { name }
  obj.eat = () => `${name} eats`
  return obj
}
function createDog(name) {
  const dog = createAnimal(name)   // "inherit" by starting from a base
  dog.bark = () => 'woof'          // augment
  return dog
}

It uses composition/closures instead of the prototype chain. Methods aren't shared (memory cost), but it's simple and avoids this/new pitfalls.

for...in walks inherited enumerable properties too, so an object that inherits from another can expose more keys than you expect.

const proto = { shared: 1 }
const obj = Object.create(proto)
obj.own = 2
for (const k in obj) console.log(k)   // 'own' AND 'shared'

Guard with Object.hasOwn(obj, k), or iterate Object.keys(obj) (own only). This is why adding enumerable properties to built-in prototypes is dangerous — it pollutes every for...in.

Inherited accessors run with the instance as this, so a child can override a getter/setter while the parent's still computes against the child's data.

const base = {
  get label() { return `[${this.name}]` },
}
const item = Object.create(base)
item.name = 'box'
item.label   // '[box]' — inherited getter, `this` is item

Overriding an inherited setter and writing to the property creates an own data property unless the child also defines an accessor — a subtle source of bugs when mixing data and accessor properties across a chain.

Deep inheritance chains are rigid: a change in a base ripples to all descendants, and an object is locked into one hierarchy. Composition builds objects from small, swappable behaviors, which is more flexible.

const withTimestamp = (o) => ({ ...o, createdAt: Date.now() })
const withId = (o) => ({ ...o, id: crypto.randomUUID() })
const record = withId(withTimestamp({ name: 'Ada' }))

Compose capabilities (functions, mixins, delegation) instead of inheriting them. This avoids the fragile-base-class problem and the "gorilla holding the banana and the entire jungle" inheritance trap.

instanceof returns true for any constructor whose prototype is in the chain, so it sees inherited types.

class Shape {}
class Circle extends Shape {}
const c = new Circle()
c instanceof Circle   // true
c instanceof Shape    // true (ancestor)

For prototypal (non-class) inheritance, use proto.isPrototypeOf(obj) to ask "is this object in obj's chain?" — the delegation-style equivalent of instanceof.

proto.isPrototypeOf(obj) returns true if proto appears anywhere in obj's prototype chain — the object-oriented complement to instanceof (which works with constructors).

const animal = {}
const dog = Object.create(animal)
animal.isPrototypeOf(dog)            // true
Object.prototype.isPrototypeOf(dog)  // true

It's the natural type check for prototypal inheritance built with Object.create, where there's no constructor to use with instanceof.

An inherited method is called on the instance, so this is the instance, not the prototype where the method is defined.

const proto = { whoAmI() { return this.name } }
const a = Object.create(proto); a.name = 'A'
const b = Object.create(proto); b.name = 'B'
a.whoAmI()  // 'A'
b.whoAmI()  // 'B' — same method, different `this`

This is what makes shared prototype methods useful: one definition operates on whichever instance invokes it. Lose the receiver (extract the method) and this breaks, as always.

Yes — chains can be arbitrarily deep, each level delegating to the next.

const a = { x: 1 }
const b = Object.create(a); b.y = 2
const c = Object.create(b); c.z = 3
c.x  // 1 (from a, two levels up)
c.y  // 2 (from b)
c.z  // 3 (own)

Lookup walks c -> b -> a -> Object.prototype -> null. Deep chains work but get harder to reason about and slightly slower to traverse; flatter structures (or composition) are usually clearer.

new.target is the constructor that was invoked with new (or undefined for a normal call). It lets a base constructor know which subclass is being built and enforce new.

function Base() {
  if (!new.target) throw new Error('Use new')
  console.log(new.target.name)   // logs the actual subclass
}
class Sub extends Base {}
new Sub()   // logs 'Sub'

Useful for abstract base classes (throw if new.target === Base) and for factory logic that depends on the concrete type being constructed.

No — JSON.stringify serializes own enumerable properties only, ignoring anything inherited from the prototype.

const proto = { inherited: 1 }
const obj = Object.create(proto)
obj.own = 2
JSON.stringify(obj)   // '{"own":2}' — inherited is omitted

So data on a prototype won't be serialized. If you need inherited values in JSON, copy them onto the object first ({ ...proto, ...obj }) or implement toJSON.

For most code, use class — it's clearer, standard, and handles the prototype wiring (extends/super/statics) correctly. Reach for raw prototypes/Object.create only for low-level work, OLOO-style delegation, or understanding internals.

// idiomatic modern inheritance
class Animal { constructor(n) { this.name = n } speak() {} }
class Dog extends Animal { speak() { return 'Woof' } }

Classes and prototypes are the same machinery; classes just give you a safer, more readable interface. Knowing prototypes still matters for debugging and edge cases.

Methods added to each instance are duplicated per object (more memory); methods on the prototype are shared (one copy).

// a new function per instance
function User(name) {
  this.name = name
  this.greet = function () { return this.name }
}
// shared once
User.prototype.greet = function () { return this.name }

For a handful of objects it doesn't matter; for thousands, prototype methods save significant memory. Factory functions trade this away for closure-based privacy — a deliberate choice.

Both are "delegation" but different ideas. Prototypal delegation forwards property lookups up the prototype chain. Event delegation attaches one listener to a parent that handles events from many children via bubbling.

// event delegation (unrelated to prototypes)
list.addEventListener('click', (e) => {
  if (e.target.matches('li')) handle(e.target)
})

They share a name and a "let something else handle it" spirit, but operate in completely different parts of the language/DOM — a common point of confusion.

Spread/Object.assign copy own properties but give the clone a plain object prototype, losing the original's prototype. Preserve it explicitly:

const clone = Object.assign(
  Object.create(Object.getPrototypeOf(original)),
  original,
)
// or structuredClone — but it does NOT preserve custom prototypes either

{ ...obj } and structuredClone both drop the custom prototype (the clone becomes a plain object). To keep behavior, create the clone with Object.create(getPrototypeOf(original)) then copy the own properties.

Practice tests are coming soon

Get notified when interactive mock interviews and quizzes launch.