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 GuideA 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.
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.