Prototypes & the Prototype Chain Interview Questions & Answers
29 questions Updated 2026-06-18
JavaScript prototype interview questions — the prototype chain, __proto__ vs prototype, property lookup, inheritance, and how methods are shared.
Read the in-depth guideJavaScript Prototypes & the Prototype Chain Explained — A Visual GuideEvery object has an internal link to another object called its prototype. When you access a property that the object doesn't have, JavaScript looks it up on the prototype, then that object's prototype, and so on — the prototype chain. This is how objects inherit behavior in JavaScript.
const arr = [1, 2, 3]
arr.map(x => x) // `map` isn't on `arr`; it's found on Array.prototype
Prototypes are how methods are shared across many instances without copying them onto each object — the foundation of JavaScript's object model.
They're related but distinct:
prototypeis a property of constructor functions (and classes). It's the object that becomes the prototype of instances created withnew.__proto__(orObject.getPrototypeOf) is the actual prototype link on every object — pointing to the object it inherits from.
function Dog() {}
const d = new Dog()
d.__proto__ === Dog.prototype // true
Object.getPrototypeOf(d) === Dog.prototype // true (preferred check)
So Constructor.prototype is what instances get as their __proto__. Use
Object.getPrototypeOf/setPrototypeOf instead of the legacy __proto__.
When you read obj.prop, the engine checks obj first; if not found, it
follows obj's prototype, then that object's prototype, until it finds the
property or reaches null (returning undefined).
const animal = { eats: true }
const dog = Object.create(animal)
dog.barks = true
dog.barks // true (own)
dog.eats // true (inherited from animal)
dog.flies // undefined (not found anywhere)
Writing a property, by contrast, always creates/updates an own property on the object itself — it doesn't modify the prototype (unless an inherited setter intercepts it).
The chain ends at null. Most objects inherit from Object.prototype,
whose prototype is null.
const obj = {}
Object.getPrototypeOf(obj) === Object.prototype // true
Object.getPrototypeOf(Object.prototype) === null // true
Object.prototype provides toString, hasOwnProperty, valueOf, etc., which
is why nearly every object has them. An object made with Object.create(null)
has no prototype, so its chain is empty.
Putting methods on the prototype means one shared copy for all instances, instead of a separate function per object — saving memory and allowing methods to be updated in one place.
function User(name) { this.name = name }
User.prototype.greet = function () { return `Hi, ${this.name}` }
const a = new User('Ada'), b = new User('Bob')
a.greet === b.greet // true — shared method
Each instance holds only its own data (name); the behavior lives once on
User.prototype. Classes do this automatically (methods go on the prototype).
Use hasOwnProperty / Object.hasOwn, which check only own properties,
unlike in which also sees inherited ones.
const dog = Object.create({ eats: true })
dog.barks = true
'eats' in dog // true (inherited)
Object.hasOwn(dog, 'eats') // false (not own)
Object.hasOwn(dog, 'barks')// true
Object.hasOwn (ES2022) is the modern, robust form — it works even on
Object.create(null) objects and can't be shadowed by an own property named
hasOwnProperty.
Use Object.getPrototypeOf to read and Object.setPrototypeOf (or
Object.create) to set — prefer these over the legacy __proto__.
const proto = { greet() { return 'hi' } }
const obj = Object.create(proto) // create WITH a prototype
Object.getPrototypeOf(obj) === proto // true
Object.setPrototypeOf(obj, otherProto) // change it (slow — avoid in hot code)
Object.setPrototypeOf deoptimizes the object and is slow; set the prototype at
creation time with Object.create or a constructor/class instead.
class is syntactic sugar over prototypes. Class methods are put on the
constructor's prototype, and extends sets up the prototype chain — the same
mechanism, nicer syntax.
class User {
constructor(name) { this.name = name }
greet() { return `Hi, ${this.name}` } // -> User.prototype.greet
}
typeof User // 'function'
User.prototype.greet // the method lives here
Under the hood there are no "real" classes — class desugars to constructor
functions + prototype assignment, with some extras (strict mode, non-enumerable
methods, new-only invocation).
An own property with the same name as an inherited one shadows (hides) the prototype's version — lookup stops at the own property.
const proto = { greet: () => 'from proto' }
const obj = Object.create(proto)
obj.greet = () => 'from obj'
obj.greet() // 'from obj' — shadows the prototype's greet
delete obj.greet
obj.greet() // 'from proto' — inherited version reappears
Assigning to an inherited property creates an own one (shadowing); it doesn't change the prototype. Deleting the own property reveals the inherited one again.
obj instanceof Ctor checks whether Ctor.prototype appears anywhere in
obj's prototype chain.
class Animal {}
class Dog extends Animal {}
const d = new Dog()
d instanceof Dog // true
d instanceof Animal // true (Animal.prototype is in the chain)
d instanceof Object // true
So it tests prototype-chain membership, not the exact constructor. It can break
across realms (iframes) and if you reassign prototype; Symbol.hasInstance
can customize its behavior.
Every function's prototype object has a constructor property pointing back
to the function. Instances inherit it, so you can find the constructor that made
an object.
function User() {}
User.prototype.constructor === User // true
const u = new User()
u.constructor === User // true (inherited)
Gotcha: if you replace Prototype entirely (User.prototype = {...}), you
lose the constructor link unless you set it back. It's mostly informational
and shouldn't be relied on for type checks.
You technically can (Array.prototype.last = function(){...}), but you
shouldn't — modifying built-in prototypes (monkey-patching) risks clashing
with future language features or other libraries, and breaks for...in.
// avoid — pollutes every array, may collide with a future Array.prototype
Array.prototype.last = function () { return this[this.length - 1] }
Prefer standalone utility functions or subclasses. The one time it's acceptable is a carefully scoped polyfill that implements a standardized method on old engines.
A security vulnerability where untrusted input modifies Object.prototype,
affecting every object in the program — often via unsafe deep-merge or
property assignment using a __proto__ key.
// attacker payload: { "__proto__": { "isAdmin": true } }
merge({}, JSON.parse(userInput))
;({}).isAdmin // true — every object now "has" isAdmin
Mitigate by rejecting __proto__/constructor/prototype keys, using
Object.create(null) or Map for user data, and Object.freeze(Object. prototype). It's a real-world exploit in many libraries.
They live on the built-in prototypes — Array.prototype, String.prototype,
etc. — and are inherited by every array/string via the prototype chain.
[1, 2].map === Array.prototype.map // true
'hi'.toUpperCase === String.prototype.toUpperCase // true
That's why all arrays share the same map/filter. Primitives like strings are
temporarily boxed into wrapper objects so they can reach their prototype
methods, then discarded.
Use call/apply to invoke a prototype method with a different this — handy
for array-like objects that lack array methods.
function sum() {
return Array.prototype.reduce.call(arguments, (a, b) => a + b, 0)
}
Array.prototype.slice.call(document.querySelectorAll('div')) // NodeList -> array
Method borrowing leverages that prototype methods are just functions decoupled
from their objects via this. Modern code often uses Array.from/spread
instead, but borrowing shows how the chain and this interact.
Object.keys— own enumerable properties only.for...in— own and inherited enumerable properties.
const proto = { inherited: 1 }
const obj = Object.create(proto)
obj.own = 2
Object.keys(obj) // ['own']
for (const k in obj) console.log(k) // 'own', then 'inherited'
This is why for...in needs a hasOwn guard. Built-in prototype methods are
non-enumerable, so they don't show up in for...in — only your own additions
to prototypes would.
Object.create(proto)sets the new object's prototype toprotoand runs no constructor.new Fn()creates an object whose prototype isFn.prototype, then runsFnas a constructor (initializing the object).
const a = Object.create(protoObj) // prototype = protoObj, no init
const b = new Ctor(args) // prototype = Ctor.prototype, runs Ctor
Object.create is lower-level (just sets up the link); new is the full
constructor flow. new Ctor() is roughly Object.create(Ctor.prototype) plus
calling Ctor with the new object as this.
JavaScript's prototype model is delegation-based: objects don't copy properties from a "class," they delegate missing property lookups to their prototype at runtime. Each object stores only what differs from its prototype.
const base = { type: 'shape', area() { return 0 } }
const circle = Object.create(base)
circle.r = 2
circle.area = function () { return Math.PI * this.r ** 2 } // override only this
Changes to the prototype are immediately visible to all delegating objects (a live link), unlike class-based copying. This is why the model is sometimes called "objects linking to other objects" (OLOO).
Because the prototype link is live, existing instances immediately see changes to their prototype.
function User() {}
const u = new User()
User.prototype.greet = function () { return 'hi' }
u.greet() // 'hi' — added after u was created, but visible
This is a consequence of delegation: instances don't copy the prototype, they
reference it. (Reassigning the whole prototype object, however, only affects
future instances — existing ones keep their old prototype.)
typeof— for primitives and detecting functions ('string','number','function', …).instanceof— for checking whether an object is built by a particular constructor/class (prototype-chain membership).
typeof 'hi' // 'string'
typeof [] // 'object' (useless for arrays)
[] instanceof Array // true
new Date instanceof Date // true
typeof null is 'object' and typeof [] is 'object', so use
Array.isArray for arrays and instanceof/duck-typing for object kinds.
No. Arrow functions lack a prototype property and can't be used with
new, because they're not designed to be constructors.
const arrow = () => {}
arrow.prototype // undefined
new arrow() // TypeError: arrow is not a constructor
function regular() {}
regular.prototype // {} — usable as a constructor
Only regular functions (and classes) have a prototype object for building
instances. This is one of several reasons arrows are lightweight callbacks, not
general-purpose functions.
Slightly — each missing-property lookup walks the chain until found or null,
so very deep chains add overhead. In practice engines optimize this heavily
(inline caches), so it rarely matters for normal depths.
// accessing a deeply inherited property walks more links
deep.someInheritedMethod() // traverses several prototypes
Bigger wins come from not reassigning prototypes at runtime
(setPrototypeOf deoptimizes) and keeping object "shapes" stable. Don't
micro-optimize chain depth; do avoid dynamic prototype mutation in hot paths.
Since JavaScript has single-prototype inheritance, you compose extra behavior by copying methods onto a prototype — a mixin.
const serializable = { toJSON() { return { ...this } } }
const loggable = { log() { console.log(this) } }
class Model {}
Object.assign(Model.prototype, serializable, loggable)
new Model().log()
Object.assign(Class.prototype, ...mixins) adds shared methods without a
superclass — the standard way to share behavior across unrelated classes.
Object.create(null) objects lack Object.prototype, so the usual methods
(toString, hasOwnProperty, valueOf) are missing.
const dict = Object.create(null)
dict.toString() // TypeError: not a function
`${dict}` // throws — can't coerce to string
Object.keys(dict) // static methods still work
dict.hasOwnProperty // undefined -> use Object.hasOwn(dict, k)
Use the static Object.* methods instead of instance methods. The benefit
(no inherited keys to collide with) is why they're used as safe maps — but a
Map usually avoids these pitfalls entirely.
Changing an existing object's prototype forces engines to deoptimize it (its hidden class/shape changes), which is slow and can hurt every later access to that object.
// slow — mutates an existing object's prototype
const obj = {}
Object.setPrototypeOf(obj, proto)
// fast — set the prototype at creation
const obj2 = Object.create(proto)
Set the prototype once at creation (Object.create, a class, or a
constructor) rather than mutating it later. MDN explicitly warns about
setPrototypeOf's performance impact.
Neither is universally better — they solve different problems. Prototypal inheritance shares behavior down an is-a chain; composition builds behavior by combining small, independent pieces (often favored for flexibility).
// composition: assemble capabilities
const canFly = (s) => ({ fly: () => `${s.name} flies` })
const bird = (name) => { const s = { name }; return { ...s, ...canFly(s) } }
Deep inheritance chains get rigid; composition (mixins, factory functions, delegation) tends to scale better. The common guidance "favor composition over inheritance" applies in JS too.
Yes — accessor properties defined on a prototype are inherited and run with the
instance as this, so derived values work per instance.
class Circle {
constructor(r) { this.r = r }
get area() { return Math.PI * this.r ** 2 } // on Circle.prototype
}
new Circle(2).area // 12.56... — `this` is the instance
Class getters/setters live on the prototype (shared definition), but execute
against each instance. You can also add them via
Object.defineProperty(proto, 'x', { get, set }).
Each realm (iframe, worker, Node vm) has its own built-in constructors and
prototypes. An array from another frame has a different Array.prototype, so
arr instanceof Array is false in your realm.
const iframeArray = iframe.contentWindow.eval('[1, 2]')
iframeArray instanceof Array // false — different Array constructor
Array.isArray(iframeArray) // true — realm-agnostic
Prefer realm-safe checks: Array.isArray, Object.prototype.toString.call(x)
('[object Array]'), or duck typing — not instanceof — for cross-realm code.
"Objects Linking to Other Objects" — a style (popularized by Kyle Simpson) that
uses Object.create and delegation directly, without constructors, new, or
classes, embracing JavaScript's prototypal nature.
const Widget = {
init(w) { this.width = w; return this },
render() { return `width: ${this.width}` },
}
const button = Object.create(Widget).init(100)
button.render() // 'width: 100'
It avoids the new/this/prototype ceremony and makes the delegation
explicit. It's a minority style — classes are more common — but it clarifies how
prototypes actually work.
Practice tests are coming soon
Get notified when interactive mock interviews and quizzes launch.