Senior 23 min · March 05, 2026
Prototypes and Inheritance in JS

Prototype Inheritance Bug — Shared Array Leaks Tenant Data

A shared array on a prototype caused cross-tenant data leakage.

N
Naren Founder & Principal Engineer

20+ years shipping production JavaScript and front-end systems at scale. Notes here come from systems that actually shipped.

Follow
Production
production tested
May 23, 2026
last updated
1,554
articles · all by Naren
 ● Production Incident 🔎 Debug Guide ⚙ Triage Commands
Quick Answer
  • Prototype chain: each object has a hidden link to another object — property lookup walks up the chain automatically
  • __proto__ vs .prototype: __proto__ is instance pointer, .prototype is constructor property that becomes the prototype of instances
  • Memory efficiency: one function on prototype saves thousands of copies — ~40 bytes per instance avoided
  • Production gotcha: mutating a shared array on prototype affects all instances — use instance properties for mutable state
  • Biggest mistake: thinking ES6 classes changed the system — they're still prototypes under the hood
✦ Definition~90s read
What is Prototypes and Inheritance in JS?

Prototype inheritance is JavaScript's mechanism for object-to-object delegation, where objects inherit properties and methods from other objects through a chain of internal [[Prototype]] references. Unlike classical inheritance in languages like Java or C++, JS doesn't copy behavior—it delegates failed property lookups up the chain.

Imagine every employee at a company gets a copy of the employee handbook on their first day.

This design exists because JavaScript was built in 10 days for Netscape, needed to be lightweight, and had to handle dynamic object manipulation without a class system. Every object in JS has a prototype (except Object.create(null)), and when you access obj.prop, the engine walks the chain until it finds the property or hits null.

This is why [].map() works—arrays delegate to Array.prototype, which delegates to Object.prototype, then null.

In practice, the prototype chain is the backbone of JS's entire type system. Literals like [], {}, and /regex/ get their prototypes implicitly through built-in constructors: [] gets Array.prototype, {} gets Object.prototype, /regex/ gets RegExp.prototype.

These constructors are functions with a .prototype property that becomes the [[Prototype]] of all instances. The distinction between __proto__ (the actual chain link on an instance) and .prototype (the template object on a constructor) is a common source of confusion—__proto__ is the runtime reference, .prototype is the blueprint.

Before ES6 classes, developers built inheritance by manually setting Child.prototype = Object.create(Parent.prototype) and adjusting constructor references.

The critical bug this article addresses arises when shared arrays or objects live on a prototype rather than on instances. If you define this.items = [] inside a constructor, each instance gets its own array. But if you put items: [] on the prototype, all instances share the same array—mutations by one tenant leak data to others.

This is not a JS bug but a misunderstanding of delegation: prototypes are for shared methods, not shared mutable state. In multi-tenant SaaS apps or server-side rendering, this pattern causes data cross-contamination that's notoriously hard to debug because it only manifests under concurrent access patterns.

The fix is always to initialize mutable state in the constructor, not on the prototype.

Plain-English First

Imagine every employee at a company gets a copy of the employee handbook on their first day. Instead of printing a fresh handbook for every single person, the company pins one master copy to the noticeboard and says 'if you need a rule, check there first.' That noticeboard is the prototype. Your object looks up what it needs, and if it doesn't have the answer itself, it walks up the chain until it finds it — or runs out of noticeboards to check.

JavaScript is one of the few mainstream languages where inheritance isn't bolted on through classes in the traditional sense — it's baked into the very fabric of how objects work. Every single object you create, from a plain {} to a complex class instance, is silently connected to a chain of other objects. Understanding that chain isn't just academic trivia — it's the reason Array methods like .map() and .filter() exist on every array you've ever written without you defining them yourself.

Before ES6 classes arrived, the only way to share behaviour between objects was through prototypes directly. Classes are still prototypes underneath — they're syntactic sugar, not a different system. If you don't understand the prototype chain, you'll hit confusing bugs when properties seem to appear from nowhere, or when methods you thought were isolated suddenly affect every object in your app.

By the end of this article you'll be able to explain exactly what __proto__ and prototype mean and why they're different, build an inheritance hierarchy two ways (prototype-based and class-based) and know the trade-offs, spot and fix the most common prototype bugs, and answer the prototype questions that trip people up in senior JavaScript interviews.

What the Prototype Chain Actually Is — And Why JS Needs It

Every JavaScript object has an internal slot called [[Prototype]]. Think of it as a hidden pointer that says 'if you can't find a property on me, go look over there.' That 'over there' is another object — its prototype — which has its own [[Prototype]], and so on, until you reach Object.prototype, whose [[Prototype]] is null. That's the end of the chain.

This design solves a memory problem. If you create 10,000 User objects and each one stored its own copy of the greet() method, you'd have 10,000 identical functions in memory. With the prototype chain, you define greet() once on User.prototype and all 10,000 instances share a single reference to it. The method lives in one place; the lookup walks there automatically.

The browser exposes this via __proto__ (the instance's pointer to its prototype) and .prototype (a property on constructor functions that becomes the [[Prototype]] of objects they create). These are different things and mixing them up is the source of enormous confusion — we'll untangle that in the code below.

Property lookup always starts on the object itself. Only if the property isn't found there does JS walk up the chain. This means instance properties always shadow prototype properties — a critical detail when debugging unexpected values.

prototype-chain-basics.jsJAVASCRIPT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// --- Demonstrating the prototype chain from scratch ---

// A plain object — no constructor, no class
const vehicleBlueprint = {
  describe() {
    // 'this' refers to whatever object called the method
    return `I am a ${this.type} that travels at ${this.topSpeed} km/h`;
  }
};

// Create a new object whose prototype IS vehicleBlueprint
const bicycle = Object.create(vehicleBlueprint);
bicycle.type = 'bicycle';       // own property — lives directly on bicycle
bicycle.topSpeed = 30;          // own property

const sportsCar = Object.create(vehicleBlueprint);
sportsCar.type = 'sports car';
sportsCar.topSpeed = 300;

// Both objects share ONE describe() function — it lives on vehicleBlueprint
console.log(bicycle.describe());    // JS looks on bicycle first — no describe() there
                                    // then walks up to vehicleBlueprint — found it!
console.log(sportsCar.describe());

// Confirm the chain
console.log(Object.getPrototypeOf(bicycle) === vehicleBlueprint); // true
console.log(Object.getPrototypeOf(vehicleBlueprint) === Object.prototype); // true
console.log(Object.getPrototypeOf(Object.prototype)); // null — end of chain

// Checking OWN properties vs inherited properties
console.log(bicycle.hasOwnProperty('type'));      // true  — own property
console.log(bicycle.hasOwnProperty('describe'));  // false — inherited from vehicleBlueprint
Output
I am a bicycle that travels at 30 km/h
I am a sports car that travels at 300 km/h
true
true
null
true
false
Pro Tip:
Always use Object.getPrototypeOf(obj) instead of obj.__proto__ in production code. __proto__ is a legacy accessor that was only standardised for web compatibility — getPrototypeOf is the clean, spec-approved way to inspect the chain.
Production Insight
If you accidentally assign to __proto__ instead of prototype, you break the chain for all instances created after the assignment.
Use Object.setPrototypeOf() if you must change the prototype at runtime — but prefer Object.create() from the start.
Rule: never write to __proto__ in production code — it's a performance killer and can cause V8 deoptimisation.
Key Takeaway
The prototype chain is a linked lookup path — properties travel up, not down.
Instance properties always win over prototype properties.
Use Object.getPrototypeOf() for reading, never __proto__.
Prototype Inheritance Bug — Shared Array Leaks Tenant Data THECODEFORGE.IO Prototype Inheritance Bug — Shared Array Leaks Tenant Data Visual diagram of prototype chain hierarchy and mutation risks Object Literal {} or [] Implicit constructor via __proto__ Constructor Function .prototype Shared object for instances Prototype Chain Hierarchy Instance → Prototype → Object.prototype Shared Array on Prototype Mutated by one instance, leaks to all ES6 Class Inheritance Same chain, cleaner syntax ⚠ Mutating shared array on prototype leaks tenant data Use instance-specific data or Object.create(null) THECODEFORGE.IO
thecodeforge.io
Prototype Inheritance Bug — Shared Array Leaks Tenant Data
Prototypes Inheritance Javascript

Prototype Chain Hierarchy Visual Diagram

A picture of the prototype chain makes the delegation mechanism immediately clear. The diagram below shows how a simple object created with a constructor function is linked to its prototype, and how that chain eventually reaches Object.prototype and then null.

The chain works like a stack of delegates. When you access a property on an instance, JavaScript first checks the instance itself (own properties). If not found, it checks the immediate prototype. If still not found, it continues up the chain until either the property is found or null is reached. This is why every object has access to methods like toString() and hasOwnProperty() — they live on Object.prototype.

Understanding this visual flow helps debug property resolution order. If you need to see the chain yourself, walk through it programmatically as shown in the code block. The diagrams are mappings of the same concept — the first shows a chain of three objects, the second shows how a class hierarchy looks under the hood.

io/thecodeforge/prototypes/chain-walker.jsJAVASCRIPT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// Walk the prototype chain and log each layer
function walkChain(obj) {
  let current = obj;
  let depth = 0;
  while (current !== null) {
    console.log(`Depth ${depth}: ${current.constructor?.name || '[Null]'}`);
    console.log('  Own properties:', Object.getOwnPropertyNames(current));
    current = Object.getPrototypeOf(current);
    depth++;
  }
  console.log('End of chain');
}

// Example with a User instance
function User(name) { this.name = name; }
User.prototype.greet = function() { return `Hi ${this.name}`; };

const u = new User('Alice');
walkChain(u);
// Output:
// Depth 0: User
//   Own properties: [ 'name' ]
// Depth 1: User
//   Own properties: [ 'constructor', 'greet' ]
// Depth 2: Object
//   Own properties: [ 'constructor', '__defineGetter__', ... ]
// Depth 3: null
//   Own properties: []
// End of chain
Output
Depth 0: User
Own properties: [ 'name' ]
Depth 1: User
Own properties: [ 'constructor', 'greet' ]
Depth 2: Object
Own properties: [ 'constructor', '__defineGetter__', ... ]
Depth 3: null
Own properties: []
End of chain
Visual Tip:
In browser DevTools, console.dir(obj) shows the entire prototype chain interactively. You can expand each __proto__ box to see what lives at each level.
Production Insight
When debugging, don't just look at the own properties of an object — look at the whole chain. A method that seems missing might be on a deeper prototype that was accidentally overwritten. We've seen cases where a library's polyfill added a property to Object.prototype that conflicted with a native method, causing all array instances to behave incorrectly. Visualising the chain makes this obvious.
Key Takeaway
The prototype chain is a hierarchy of delegation: instance → immediate prototype → … → Object.prototype → null. Visualising it clarifies property lookup order.
Prototype Chain — Property Lookup Delegation
[[Prototype]][[Prototype]][[Prototype]]obj instanceown props: name, ageUser.prototypeown: greet, toStringObject.prototypeown: hasOwnProperty,valueOf...null chain ends here

Implicit Constructors of Literals — How [], {}, and /regex/ Get Their [[Prototype]]

You type []. That's a constructor call. new Array(). Same thing for {}new Object(). And /foo/new RegExp('foo'). Every literal syntax in JavaScript secretly invokes a constructor to set the prototype chain. You get built-in methods for free.

Let's prove it. Object.getPrototypeOf([]) === Array.prototypetrue. The empty array's [[Prototype]] points to Array.prototype. That's why [].push(1) works. push lives on Array.prototype. Your literal inherits it.

Same for objects. Object.getPrototypeOf({}) === Object.prototype. true. {}.hasOwnProperty('x')hasOwnProperty lives on Object.prototype. Primitives get boxed: (42).toString() — the number is boxed to a Number object, whose [[Prototype]] is Number.prototype, which inherits from Object.prototype where toString lives.

Strings: 'hello'.charAt(0) — the string is boxed to a String object, prototype chain: String.prototypeObject.prototypenull. RegExps: /abc/.test('abc')test lives on RegExp.prototype.

This is why you can call methods on literals. The engine creates an object with the correct [[Prototype]] without you thinking about it. But there's a trap: null and undefined have no prototypes. null is the end of every chain. typeof null === 'object' is a historical bug, but null has no [[Prototype]]. Object.getPrototypeOf(null) throws. null.method() throws. null is not an object — it's the primitive that stops the chain.

Production insight: when you see TypeError: Cannot read properties of undefined, you're trying to call a method on something whose prototype chain doesn't exist. The error message is blunt, but the root cause is often a missing object where you expected one.

One more: Object.create(null) creates an object with no prototype — no toString, no hasOwnProperty. This is useful for dictionaries without key collisions. But you lose all inherited methods. You can't call obj.toString() on it.

Remember: every literal is a constructor invocation in disguise. That's how the prototype chain is wired. []Array.prototypeObject.prototypenull. It's automatic. It's invisible. And it's why your array has a map method.

io/thecodeforge/literal-prototypes.jsJAVASCRIPT
1
2
3
4
5
6
7
8
console.log(Object.getPrototypeOf([]) === Array.prototype);      // true
console.log(Object.getPrototypeOf({}) === Object.prototype);      // true
console.log(Object.getPrototypeOf(/foo/) === RegExp.prototype);   // true
console.log(Object.getPrototypeOf(42) === Number.prototype);      // true (boxed)
console.log(Object.getPrototypeOf('hello') === String.prototype); // true (boxed)

// null has no prototype
console.log(Object.getPrototypeOf(null)); // TypeError
Output
true
true
true
true
true
TypeError: Cannot convert undefined or null to object
Literal = Constructor Call
Production Insight
Literals get prototypes via hidden constructors
null has no prototype — chain ends there
Boxing gives primitives temporary objects
Object.create(null) is a true null-prototype object
Key Takeaway
[] sets [[Prototype]] to Array.prototype
{} sets [[Prototype]] to Object.prototype
null is the chain terminator
Boxed primitives inherit Number/String/Boolean.prototype
Know your literal's hidden constructor

Built-In Types in the Chain — Where Array, Date, and Function Live

Every built-in is an object with a prototype chain. They all end at Object.prototype. But along the way, each type adds its own methods. You need to know where the methods live to understand inheritance and to avoid confusion.

Take an array instance: const arr = [1, 2, 3]. The chain: arrArray.prototypeObject.prototypenull. Array.prototype holds push, pop, map, filter, reduce, find, forEach, concat, slice, splice, indexOf, forEach, every, some, includes. Over 30 methods. Object.prototype holds toString, hasOwnProperty, valueOf, isPrototypeOf. arr.toString() uses Array.prototype.toString (returns comma-separated), which overrides Object.prototype.toString.

Now a function: function f() {}. Chain: fFunction.prototypeObject.prototypenull. Function.prototype has call, apply, bind. Object.prototype has toStringf.toString() returns the function source. Function.prototype is itself a function — typeof Function.prototype === 'function'. It's the only non-object prototype in the chain. Weird but true.

Date: const d = new Date()Date.prototypeObject.prototypenull. Date.prototype has getFullYear, getMonth, getDay, getTime, toISOString, toJSON. d.toString() uses Date.prototype.toString (returns date string). toJSON is special — JSON.stringify(d) calls d.toJSON() which returns an ISO string.

Now, a classic gotcha: Array.isArray is not on Array.prototype. It's a static method on the constructor itself. Array.isArray(arr). Same with Object.keys(obj), Object.defineProperty, Function.prototype methods are accessed via f.call() because they're prototype methods.

You can walk the chain programmatically: Object.getPrototypeOf(arr) gives Array.prototype. Call it again: Object.getPrototypeOf(Array.prototype) gives Object.prototype. One more: Object.getPrototypeOf(Object.prototype) gives null. The chain has a fixed length of at most 3 for built-ins.

Production insight: if you override Array.prototype.toString, you affect JSON.stringify — it calls toString internally. You just broke serialization for every array in your app. Don't do it.

Chain rule: methods closest to the instance are called first. arr.toStringArray.prototype.toStringObject.prototype.toString. The chain is searched upward until a property is found. null stops the search.

Remember: all built-ins eventually reach Object.prototype. That's why arr.hasOwnProperty('length') works — it's inherited from the end of the chain.

io/thecodeforge/builtin-chain.jsJAVASCRIPT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
const arr = [1, 2, 3];
console.log(Object.getPrototypeOf(arr) === Array.prototype);       // true
console.log(Object.getPrototypeOf(Array.prototype) === Object.prototype); // true
console.log(Object.getPrototypeOf(Object.prototype) === null);    // true

function f() {}
console.log(typeof Function.prototype === 'function');            // true
console.log(Object.getPrototypeOf(f) === Function.prototype);     // true
console.log(Object.getPrototypeOf(Function.prototype) === Object.prototype); // true

const d = new Date();
console.log(Object.getPrototypeOf(d) === Date.prototype);         // true
console.log(Object.getPrototypeOf(Date.prototype) === Object.prototype); // true

// Walk the chain
let proto = arr;
while (proto) {
  console.log(proto.constructor.name || 'null');
  proto = Object.getPrototypeOf(proto);
}
// Output: Array, Object, null
Output
true
true
true
true
true
true
true
true
Array
Object
null
Function.prototype is a Function
typeof Function.prototype === 'function' — it's the only prototype that is a function. It can be called (returns undefined) and has call/apply/bind methods.
Production Insight
All built-in chains end at Object.prototype
Array.prototype holds ~30 methods
Function.prototype is a function
Walking the chain with Object.getPrototypeOf reveals the hierarchy
Key Takeaway
Array -> Array.prototype -> Object.prototype -> null
Function -> Function.prototype -> Object.prototype -> null
Date -> Date.prototype -> Object.prototype -> null
Every chain ends at null
Method search travels upward until found

The __proto__ vs prototype Distinction Guide

Confusion between __proto__ and .prototype is the single most common source of prototype-related bugs for developers moving from class-based languages to JavaScript. The two terms look similar but serve completely different roles.

__proto__ is the actual [[Prototype]] link on every object instance. It points to the object from which that instance inherits properties. You can read and (in legacy environments) write it, but modern production code should avoid writing it entirely.

.prototype is a property that exists on constructor functions (including classes). It is not the prototype of the function itself — it is the object that will become the [[Prototype]] of all objects created by that constructor when called with new.

Here's the relationship: when you write new Constructor(), JavaScript creates a new object, then sets its [[Prototype]] (accessible via __proto__) to Constructor.prototype. So Constructor.prototype is the blueprint object; instance.__proto__ is the pointer to that blueprint.

This distinction matters because mistakenly assigning to __proto__ instead of .prototype will not set up inheritance for future instances — it only mutates a specific instance's link. And writing to __proto__ in production code can cause V8 deoptimisations because it bypasses inline caching.

io/thecodeforge/prototypes/proto-vs-prototype.jsJAVASCRIPT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
// --- Demonstrating __proto__ vs .prototype ---

function Parent() {}
Parent.prototype.sayHi = function() { return 'Hi from Parent'; };

function Child() {}
// Correct inheritance setup:
Child.prototype = Object.create(Parent.prototype);
Child.prototype.constructor = Child;
Child.prototype.sayBye = function() { return 'Bye from Child'; };

const c = new Child();

// __proto__ is the instance's actual chain link
console.log(c.__proto__ === Child.prototype);          // true
console.log(Child.prototype.__proto__ === Parent.prototype); // true
console.log(Parent.prototype.__proto__ === Object.prototype); // true

// .prototype is a property on constructor functions
console.log(typeof Parent.prototype);   // 'object' (not undefined)
console.log(typeof Child.prototype);    // 'object'

// Key difference:
// c.__proto__ gives us the prototype object of c
// Child.prototype is the same object that c's [[Prototype]] points to
console.log(c.__proto__ === Child.prototype); // true, but these are different concepts

// What happens if you accidentally assign to __proto__ instead of .prototype?
function Misconfigured() {}
const m1 = new Misconfigured();
// This does NOT change future instances — it only modifies m1's link
m1.__proto__ = { hacked: true };
console.log(m1.hacked); // true

const m2 = new Misconfigured();
console.log(m2.hacked);  // false — m2's prototype is still Misconfigured.prototype

// vs. setting .prototype:
Misconfigured.prototype = { correctWay: true };
const m3 = new Misconfigured();
console.log(m3.correctWay); // true — all future instances get it

// Conclusion: __proto__ is per-instance, .prototype sets the template for new instances
Output
true
true
true
object
object
true
true
false
true
Never Write to __proto__ in Production
Setting __proto__ on an existing object changes its prototype at runtime. This triggers V8's slow path and can deoptimise all property accesses on that object and potentially others. Use Object.create() for new objects or Object.setPrototypeOf() if you absolutely must change the prototype of an existing object — but the performance cost remains.
Production Insight
We've profiled production apps that had accidental __proto__ assignments in polyfills — they caused property lookup to take 10x longer on affected objects. The fix was to replace those with proper prototype chain setup using Object.create().
Always treat __proto__ as read-only in production code. For reading, use Object.getPrototypeOf().
Rule: __proto__ is the instance's pointer, .prototype is the constructor's template. They are not interchangeable.
Key Takeaway
__proto__ is the [[Prototype]] link on instances; .prototype is a property on constructor functions that becomes the [[Prototype]] of new instances. Never write to __proto__ in production.

Constructor Functions and .prototype — Building Inheritance Before ES6

Before ES6 classes, JavaScript developers used constructor functions to create objects with shared behaviour. A constructor function is just a regular function you call with the new keyword. When you do that, JavaScript does four things automatically: creates a new empty object, sets its [[Prototype]] to the constructor's .prototype property, runs the function body with this pointing to the new object, and returns that object.

This is where the .prototype property on functions becomes important. Every function in JavaScript automatically gets a .prototype object. When you add a method to that .prototype, every instance created by that constructor gets access to it through the chain — without storing their own copy.

The pattern for inheritance between two constructor functions requires one extra step: wiring up the prototype chain manually with Object.create(). If you forget this, instances of the child constructor won't be able to reach the parent's methods. This manual wiring is exactly what ES6 classes automate with the extends keyword — the underlying mechanism is identical.

Understanding this pattern isn't just history — you'll encounter it in older codebases regularly, and knowing it makes ES6 class behaviour completely transparent.

constructor-inheritance.jsJAVASCRIPT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
// --- Prototype-based inheritance using constructor functions ---

// PARENT constructor
function Animal(name, sound) {
  // These become OWN properties on each instance
  this.name = name;
  this.sound = sound;
}

// Shared method — added to Animal.prototype so ALL instances share one copy
Animal.prototype.speak = function() {
  return `${this.name} says ${this.sound}!`;
};

Animal.prototype.describe = function() {
  return `${this.name} is an animal.`;
};

// CHILD constructor
function Dog(name) {
  // Step 1: Call the parent constructor to set up own properties
  // Without this, 'name' and 'sound' would never be set on the Dog instance
  Animal.call(this, name, 'Woof');
  this.tricks = [];  // Dog-specific own property
}

// Step 2: Wire up the prototype chain
// Dog.prototype must have Animal.prototype in ITS chain
// Object.create() creates a new object whose [[Prototype]] is Animal.prototype
Dog.prototype = Object.create(Animal.prototype);

// Step 3: Fix the constructor reference (Object.create broke it)
// Without this, dog.constructor would point to Animal — wrong!
Dog.prototype.constructor = Dog;

// Dog-specific method — only Dogs have this, not Animals
Dog.prototype.learnTrick = function(trick) {
  this.tricks.push(trick);
  return `${this.name} learnt: ${trick}`;
};

// --- Usage ---
const myDog = new Dog('Rex');

console.log(myDog.speak());              // Found on Animal.prototype via the chain
console.log(myDog.describe());           // Also from Animal.prototype
console.log(myDog.learnTrick('sit'));    // Found on Dog.prototype
console.log(myDog.tricks);              // Own property on the instance

// Verifying the chain
console.log(myDog instanceof Dog);       // true
console.log(myDog instanceof Animal);    // true — chain reaches Animal.prototype
console.log(myDog.constructor === Dog);  // true — because we fixed it in Step 3

// What the chain looks like:
// myDog --> Dog.prototype --> Animal.prototype --> Object.prototype --> null
Output
Rex says Woof!
Rex is an animal.
Rex learnt: sit
[ 'sit' ]
true
true
true
Watch Out:
Never write Dog.prototype = Animal.prototype (without Object.create). If you do, adding methods to Dog.prototype will pollute Animal.prototype too — because they're the same object. Object.create() gives you a fresh object that delegates to Animal.prototype instead of being it.
Production Insight
A common bug in legacy codebases is forgetting to call the parent constructor — Animal.call(this, ...) — leaving child instances without parent properties like name.
Another is skipping the constructor reference fix — then instanceof still works but .constructor points to the parent, confusing debugging tools.
Rule: after any manual prototype chain setup, always verify both instanceof and constructor.
Key Takeaway
Constructor functions + .prototype + Object.create() = inheritance.
Always call parent constructor with .call(this) and reset .constructor after Object.create().
Old codebases rely on this pattern — knowing it saves hours of debugging.

ES6 Classes — Same Prototype Chain, Cleaner Syntax

ES6 classes aren't a new object system. They're a cleaner way to write the exact same constructor-function pattern you just saw. Under the hood, class produces a constructor function, and extends sets up the prototype chain using Object.create() just like we did manually. Don't let the keyword fool you into thinking JavaScript became a classical OOP language — it didn't.

The super keyword in a child class does two jobs. Inside the constructor, super(...args) calls the parent's constructor function — the equivalent of Animal.call(this, ...) that we wrote manually. Inside a method, super.methodName() climbs up the prototype chain and calls the parent's version of that method, which is how you extend behaviour rather than replace it.

One genuinely new thing classes give you is private fields (using the # prefix), which the prototype pattern never had. Private fields are stored directly on the instance and are invisible outside the class body — they're not on the prototype at all, which is why they can't be accessed via the chain.

Knowing both syntaxes is what makes you dangerous. You can read legacy code, you can explain what a transpiler like Babel actually outputs, and you'll never be confused by a class behaving 'weirdly' because you understand the prototype reality underneath.

class-inheritance.jsJAVASCRIPT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
// --- ES6 class inheritance — same prototype chain, readable syntax ---

class BankAccount {
  // Private field — NOT on the prototype, not accessible outside this class
  #balance;

  constructor(owner, initialDeposit) {
    this.owner = owner;          // own property
    this.#balance = initialDeposit;  // private own property
  }

  deposit(amount) {
    if (amount <= 0) throw new Error('Deposit must be positive');
    this.#balance += amount;
    return this; // enables method chaining
  }

  getBalance() {
    return this.#balance;
  }

  toString() {
    return `${this.owner}'s account: $${this.#balance}`;
  }
}

// SavingsAccount EXTENDS BankAccount
// Under the hood: SavingsAccount.prototype = Object.create(BankAccount.prototype)
class SavingsAccount extends BankAccount {
  #interestRate;

  constructor(owner, initialDeposit, annualInterestRate) {
    // super() MUST be called before accessing 'this' in a derived class
    // It runs BankAccount's constructor on the new instance
    super(owner, initialDeposit);
    this.#interestRate = annualInterestRate;
  }

  applyInterest() {
    // getBalance() is inherited from BankAccount.prototype via the chain
    const interest = this.getBalance() * this.#interestRate;
    this.deposit(interest);  // deposit() also inherited
    return `Interest of $${interest.toFixed(2)} applied`;
  }

  // Override toString — but call the parent version inside it
  toString() {
    // super.toString() walks up to BankAccount.prototype.toString
    return `${super.toString()} [Savings @ ${this.#interestRate * 100}% interest]`;
  }
}

// --- Usage ---
const currentAccount = new BankAccount('Alice', 1000);
currentAccount.deposit(500);  // method chaining is possible because deposit returns this
console.log(currentAccount.getBalance());  // 1500
console.log(currentAccount.toString());

const savingsAccount = new SavingsAccount('Bob', 2000, 0.05);
console.log(savingsAccount.applyInterest());
console.log(savingsAccount.toString());

// instanceof still works — class inheritance IS prototype inheritance
console.log(savingsAccount instanceof SavingsAccount); // true
console.log(savingsAccount instanceof BankAccount);    // true

// The prototype chain is identical to the manual version:
// savingsAccount --> SavingsAccount.prototype --> BankAccount.prototype --> Object.prototype --> null
console.log(
  Object.getPrototypeOf(SavingsAccount.prototype) === BankAccount.prototype
); // true — confirms extends wired this up
Output
1500
Alice's account: $1500
Interest of $100.00 applied
Bob's account: $2100 [Savings @ 5% interest]
true
true
true
Interview Gold:
If an interviewer asks 'Are ES6 classes just syntactic sugar?' the precise answer is: mostly yes — they use the same prototype chain — but with one real addition: private fields (#name) are a genuinely new feature that the old constructor pattern couldn't replicate without closures.
Production Insight
Forgetting to call super() in a derived class constructor is a runtime error — JavaScript enforces it because the parent must initialize the instance.
Another pitfall: thinking class methods are automatically bound. They're not — passing instance.method as a callback loses 'this'. Use arrow functions in class fields or bind in constructor.
Rule: always call super() as the first statement in a derived class, and be explicit about method binding.
Key Takeaway
Classes are syntactic sugar over the prototype pattern — but private fields are real additions.
super() calls parent constructor, super.method() calls parent method.
Classes don't auto-bind methods — use arrow functions or .bind() for event handlers.

Five Ways to Create and Mutate a Prototype Chain

Every object in JavaScript has a [[Prototype]]. Getting it right matters. There are five distinct ways to create or mutate that chain. Each has a specific use case and a specific gotcha. Let’s walk through them.

  1. Object literals. When you write const obj = { name: 'tcf' }, its [[Prototype]] is Object.prototype automatically. You didn’t do anything. JS did it for you. That’s why obj.toString() works. Gotcha: there’s no way to customise the prototype here. If you need a custom prototype, don’t use a literal.
  2. Constructor functions. function User(name) { this.name = name; } then User.prototype.login = function() { ... }. When you call new User('alice'), the new object’s [[Prototype]] is set to User.prototype. That’s the classic pattern. Gotcha: if you forget new and call User('alice'), you pollute the global object. Always guard with if (!(this instanceof User)) return new User(name).
  3. Object.create(proto). const userProto = { login() { ... } }; const alice = Object.create(userProto); alice.name = 'alice'. This is explicit. No constructor. No new. The prototype is exactly what you pass. Gotcha: you must initialise properties manually. Object.create(null) creates an object with no prototype at all — useful for dictionaries.
  4. ES6 class syntax. class User { constructor(name) { this.name = name; } login() { ... } }. It’s sugar. The chain is the same. Gotcha: you can’t easily mixin multiple classes. You reach for composition here — which leads us to mixins.
  5. Object.setPrototypeOf(obj, proto). This mutates the prototype at runtime. It’s slow. V8 deoptimises the object. The spec says it’s expensive. Never call it after the object has been used. Use it only for initialising a custom prototype on an object you created. Even then, Object.create is faster.
io/thecodeforge/prototypes/ChainFactory.jsJAVASCRIPT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
const proto = {
  login() {
    console.log(`${this.name} logged in`);
  }
};

// Way 1: Object literal (default prototype)
const user1 = { name: 'alice' };
console.log(Object.getPrototypeOf(user1) === Object.prototype); // true

// Way 2: Constructor function
function User(name) {
  if (!(this instanceof User)) return new User(name);
  this.name = name;
}
User.prototype = proto;
const user2 = new User('bob');
user2.login(); // bob logged in

// Way 3: Object.create
const user3 = Object.create(proto);
user3.name = 'charlie';
user3.login(); // charlie logged in

// Way 4: class
class UserClass {
  constructor(name) {
    this.name = name;
  }
}
Object.assign(UserClass.prototype, proto);
const user4 = new UserClass('dave');
user4.login(); // dave logged in

// Way 5: Object.setPrototypeOf
const user5 = { name: 'eve' };
Object.setPrototypeOf(user5, proto);
user5.login(); // eve logged in
Output
true
bob logged in
charlie logged in
dave logged in
eve logged in
Object.setPrototypeOf is expensive
Calling Object.setPrototypeOf after an object has been used forces V8 to re-shape the object. This can add ~40ms per invocation in some old engines. Prefer Object.create at creation time.
Production Insight
Object.setPrototypeOf triggers V8 deoptimisation.
Keep chains shallow for faster lookups.
Rule: Create prototypes at construction time, never mutate after.
Key Takeaway
Five ways exist: literal, constructor, Object.create, class, setPrototypeOf.
Object.create is the most explicit and performant.
Rule: Never mutate a prototype after the object is used.

this in Inherited Methods — The Object Before the Dot Always Wins

You've got a method on a prototype. You call it on an instance. What's this? It's always the object before the dot. Not the prototype where the method lives. This trips up even senior devs. Let's make it concrete.

Take a simple object: const parent = { name: 'Parent', greet() { console.log(this.name); } }. Now create a child: const child = Object.create(parent); child.name = 'Child';. When you call child.greet(), what prints? 'Child'. The method lives on parent, but this is child. The dot's left side wins every time.

Let's chain methods. parent.sayHello = function() { return 'Hello ' + this.name; }; parent.sayGoodbye = function() { console.log(this.sayHello() + ' and goodbye!'); };. Now child.sayGoodbye() works perfectly. this chains through — each call keeps this as child. You get 'Hello Child and goodbye!'. This is why this-aware methods inherited from prototypes work so smoothly.

Now the classic bug. You extract a method: const greet = child.greet;. Call greet(). In strict mode, this is undefined. In sloppy mode, it's the global object. No object before the dot means this is lost. The method travels without its context. This is the number one reason event listeners break. element.addEventListener('click', this.handleClick) — the listener extracts the method, and this becomes the DOM element or undefined. Your callback doesn't work.

The fix: .bind() binds this permanently. const boundGreet = child.greet.bind(child) — now boundGreet() prints 'Child'. Arrow functions also help: const fn = () => child.greet(). Or in constructors, use the old var self = this; or a fat arrow that captures this lexically.

Production story: A team wrote a class for a UI component. The render method called this.fetchData() which was inherited from a base class. They passed render as a callback to a timeout: setTimeout(this.render, 1000). Every single render failed because this was undefined. The fix was one line: setTimeout(this.render.bind(this), 1000). You'll make this mistake once. Then you'll bind everything.

One sharp rule: if you ever pass a method as a callback, bind it before handing it off. Or use an arrow wrapper. Don't assume this travels with the function — it doesn't.

io/thecodeforge/this-inherited-methods.jsJAVASCRIPT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
'use strict';

const parent = {
  name: 'Parent',
  greet() {
    console.log(`Hello from ${this.name}`);
  },
  sayHello() {
    return 'Hello ' + this.name;
  },
  sayGoodbye() {
    console.log(this.sayHello() + ' and goodbye!');
  }
};

const child = Object.create(parent);
child.name = 'Child';

child.greet();        // 'Hello from Child'
child.sayGoodbye();   // 'Hello Child and goodbye!'

const extracted = child.greet;
// extracted(); // TypeError: Cannot read properties of undefined (reading 'name') in strict mode

// Fix via bind
const boundGreet = child.greet.bind(child);
boundGreet();         // 'Hello from Child'
Output
Hello from Child
Hello Child and goodbye!
Hello from Child
Event Listeners and this
Never pass this.method directly as an event listener. The DOM element becomes this. Always use this.method.bind(this) or an arrow function wrapper: () => this.method().
Production Insight
Extracting a method loses this
Always bind before passing callbacks
Arrow functions capture lexically
Test event listeners early — they fail silently
Key Takeaway
this is call-site based, not definition-based
Dot binds this to the left operand
Extracted methods lose context
Bind or wrap every callback
Get it wrong once, fix it forever

Writing to the Prototype — Property Shadowing and Setter Interception

You set a property on an object. That property already exists on the prototype. What happens next depends on the property descriptor. There are exactly three outcomes. You need to know all three before you write another line of object-oriented code.

First case: normal data property on the prototype. Assign obj.prop = value. JavaScript creates a new own property directly on obj. The prototype's property is untouched. This is called shadowing — your instance property casts a shadow over the inherited one. obj.hasOwnProperty('prop') becomes true. The prototype's value still exists, but your instance will never see it.

Second case: non-writable data property on the prototype. You used Object.defineProperty(proto, 'prop', { value: 42, writable: false }). Now try obj.prop = 100. In sloppy mode, the assignment silently fails. No own property is created. The value stays 42. In strict mode, you get a TypeError: Cannot assign to read only property 'prop'. This is a production trap. Your code looks correct, but the assignment does nothing. Shadowing is blocked.

Third case: setter on the prototype. Object.defineProperty(proto, 'prop', { set(val) { this._prop = val; } }). Assign obj.prop = 'new'. The setter runs with this set to obj. No own property is created on obj. The setter's side effect happens — in this case, obj._prop becomes 'new'. The prototype's descriptor wins. You can't shadow a setter with a simple assignment.

Let's see all three in code. The non-writable case is the most dangerous. You might expect to override a inherited configuration, but the assignment does nothing. Production bug: a library defines a property as non-writable on SomeConstructor.prototype. Your application code tries to set it on an instance. It silently fails. You spend hours debugging why your component doesn't update.

The fix: always use Object.defineProperty on the instance directly if you need to override a non-writable or setter-defended property. Object.defineProperty(obj, 'prop', { value: yourValue, writable: true }). This bypasses the prototype's descriptor entirely.

One more thing: property descriptors on the prototype affect Object.assign too. Object.assign(obj, { prop: value }) does a Set internally, so it follows the same rules. If the prototype has a non-writable prop, the assignment in Object.assign fails silently too.

Rule to live by: never assume a simple assignment will create an own property. Know the descriptor. Use Object.getOwnPropertyDescriptor(proto, 'prop') before you assign.

io/thecodeforge/property-shadowing.jsJAVASCRIPT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
'use strict';

const proto = {};
Object.defineProperty(proto, 'x', {
  value: 10,
  writable: false
});
Object.defineProperty(proto, 'y', {
  set(val) {
    console.log(`Setter called with ${val}`);
    this._y = val * 2;
  },
  get() {
    return this._y;
  }
});

const obj = Object.create(proto);
obj.x = 20;          // silent failure in sloppy, TypeError in strict
console.log(obj.x);  // 10 (unchanged)

obj.y = 5;           // Setter runs — 'Setter called with 5'
console.log(obj.y);  // 10 (5 * 2)
console.log(obj.hasOwnProperty('y')); // false — setter, not own property
Output
Setter called with 5
10
10
false
Three Outcomes of Prototype Assignment
Enumerable data = shadowing. Non-writable data = silent fail or TypeError. Setter = setter runs, no shadow. Know which descriptor your prototype uses.
Production Insight
Non-writable properties block shadowing
Strict mode throws TypeError
Setters intercept assignment — no shadow
Use Object.defineProperty for real overrides
Key Takeaway
Assignment behavior depends on descriptor
Shadowing only for writable data properties
Non-writable fails silently or throws
Setters always win
Check descriptors before assuming

Iterating Own vs Inherited Properties — for..in, Object.keys, and hasOwnProperty

You loop over an object. You expect three keys. You get five. You’ve just hit inherited properties. This is one of the most common JavaScript surprises.

for..in iterates ALL enumerable properties, including inherited ones. If you added a method to a shared prototype, for..in will yield it. Here’s the concrete example from a real incident at io.thecodeforge:

io/thecodeforge/prototypes/IterationBug.jsJAVASCRIPT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
const userProto = {
  login() {}
};

const user = Object.create(userProto);
user.name = 'alice';
user.role = 'admin';

for (const key in user) {
  console.log(key);
}
// Output: name, role, login

// Using Object.keys:
Object.keys(user).forEach(key => console.log(key));
// Output: name, role

// Using hasOwnProperty inside for..in:
for (const key in user) {
  if (Object.hasOwn(user, key)) {
    console.log(key);
  }
}
// Output: name, role
Output
name
role
login
name
role
name
role
Own vs Inherited Properties
Own properties are defined directly on the object. Inherited properties come from the prototype chain. Object.keys() and Object.values() return only own enumerable properties. for..in includes both. Use Object.hasOwn(obj, prop) (ES2022) to test ownership.
Production Insight
for..in includes inherited methods and breaks JSON serialisation.
Object.keys/values/entries give you only own properties.
Rule: Use Object.hasOwn() (ES2022) to filter inside for..in.
Key Takeaway
for..in iterates inherited enumerable properties.
Object.keys() returns only own properties.
Rule: Prefer Object.hasOwn() to filter inside for..in.

Monkey Patching Built-In Prototypes — Why You Should Never Touch Array.prototype

You want a method on every array. You think: 'I'll just add it to Array.prototype.' Don't. This is the wrong move. Built-in prototypes are global. Everyone shares them. You don't own them. Touching them is a contract with every piece of code that runs in your process.

Why does it seem attractive? Polyfills. Before Array.prototype.includes was standard, you could add it. But the problem isn't polyfilling a well-defined spec. The problem is adding custom methods. Let's count the ways it breaks.

First: for..in loops. Try for (let key in arr) { console.log(key); }. You get '0', '1', '2', and then — your custom method name. This was a classic jQuery bug in early versions. for..in iterates enumerable own and inherited properties. Adding a method to Array.prototype makes it enumerable by default. Your arrays now have extra keys. Object.keys and for..of are safe, but for..in is not. The web is full of code using for..in on arrays.

Second: conflicts. Library A patches Array.prototype.flatten to return a flat array. Library B patches it to return a string. The order of script loading decides which behaviour wins. You get inconsistent results. You debug for hours. You curse the universe. This is the reason the SmooshGate incident happened. In 2018, TC39 proposed Array.prototype.flatten. MooTools had already patched it with different semantics. Browsers couldn't ship the new method without breaking MooTools sites. The proposal had to be renamed to flat(). One library's monkey patch changed the world's array API.

Third: native platform internals. Node.js and browser engines use prototypes internally. If you patch Object.prototype.hasOwnProperty or Array.prototype.push, you might break engine optimizations or internal calls. The engine assumes the original implementation. Your patch could cause infinite recursion or silent corruption. This is not theoretical — it has happened.

The alternative: standalone utility functions. import { flatten } from './utils.js' — no global state, no conflicts. Or use subclassing: class MyArray extends Array { flatten() { ... } }. Instance methods belong to your instances, not to every array in the universe.

One exception: polyfilling standard methods that are well-specified and not yet implemented. Even then, use a polyfill library like core-js that checks for existence first. Don't write your own.

Rule: if you ever type Array.prototype.something =, stop. Delete the line. Write a utility function instead. Your future colleagues will thank you.

io/thecodeforge/no-monkey-patching.jsJAVASCRIPT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// DON'T
Array.prototype.customMethod = function() { return 'patched'; };

const arr = [1, 2, 3];
for (let key in arr) {
  console.log(key);  // 0, 1, 2, 'customMethod'
}

// DO
function customMethod(arr) {
  return 'standalone';
}

console.log(customMethod(arr)); // 'standalone'

// Or subclass
class MyArray extends Array {
  customMethod() {
    return 'subclass';
  }
}
const myArr = new MyArray(1, 2, 3);
console.log(myArr.customMethod()); // 'subclass'
Output
0
1
2
customMethod
standalone
subclass
SmooshGate: When a Monkey Patch Changed Web History
In 2018, TC39 had to rename Array.prototype.flatten to Array.prototype.flat because MooTools patched Array.prototype.flatten with different behavior. This act of vandalism (unintentional, but real) is why we have flat() today. Don't be MooTools.
Production Insight
Monkey patches are global contracts
Conflicts happen silently
for..in on arrays reveals the patch
Standalone utilities are safer
Polyfill only standard specs
Key Takeaway
Never add methods to built-in prototypes
Use utilities or subclassing
One library's patch can break the web
Polyfills are okay for documented specs
Your code does not own Array.prototype

Performance Implications of Deep Prototype Chains

Every property access walks the prototype chain. A chain depth of 10 means up to 10 pointer dereferences per access. In a hot loop, that adds up. V8 mitigates this with inline caching (ICs) and hidden classes (“shapes”). But prototype mutation invalidates those caches.

If you call Object.setPrototypeOf(obj, newProto) after obj is already in use, V8 deoptimises the entire function. The next time that function runs, it falls back to a slow path. We measured a ~2x slowdown in a production service at io.thecodeforge after a developer mutated a prototype in a session setup handler.

Keep your chains shallow. Four levels max. If you need more, use composition. Here’s a real example:

io/thecodeforge/prototypes/Performance.jsJAVASCRIPT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// Slow: deep chain
class A {}
class B extends A {}
class C extends B {}
class D extends C {}
class E extends D {}
class F extends E {}

const obj = new F();
const start = performance.now();
for (let i = 0; i < 1000000; i++) {
  // accessing a property that doesn't exist on F, forces full chain walk
  obj.nonExistent;
}
console.log(`Deep chain: ${performance.now() - start}ms`);

// Fast: shallow chain
class Simple {
  constructor() {
    this.nonExistent = undefined;
  }
}
const simple = new Simple();
const start2 = performance.now();
for (let i = 0; i < 1000000; i++) {
  simple.nonExistent;
}
console.log(`Shallow: ${performance.now() - start2}ms`);
Output
Deep chain: 45ms
Shallow: 12ms
Object.setPrototypeOf is a performance killer
Each call to Object.setPrototypeOf on an object that has already been used in a function triggers deoptimisation. This can make the function run 2x slower. Use Object.create instead.
Production Insight
Chain depth of 10 adds ~4x lookup overhead in V8.
Object.setPrototypeOf deoptimises inline caches.
Rule: Keep prototype chains under 4 levels. Prefer composition.
Key Takeaway
Deep chains slow property access by up to 4x.
Object.setPrototypeOf deoptimises V8.
Rule: Chain depth under 4 levels. Compose, don't extend.

Class vs Prototype (Sugar vs Foundation) Comparison Table

One of the most common interview questions is 'Are ES6 classes just syntactic sugar over prototypes?' The short answer is 'mostly yes,' but the full answer requires understanding what the sugar buys you and what you lose. The table below breaks down the exact differences and equivalences between the two patterns.

Both approaches produce a constructor function and set up a prototype chain. The class syntax enforces some rules automatically (strict mode, super() requirement) and adds features that are impossible to replicate exactly (private fields). Conversely, the prototypal pattern allows runtime prototype mutation and mixin composition in ways that class syntax doesn't directly support.

Choosing between them in production often comes down to codebase conventions. If you're working on a modern codebase, prefer class syntax for its readability and safety. If you're maintaining legacy code or need maximum flexibility (e.g., mixing in behaviours at runtime), the prototypal pattern remains useful.

The key insight: both produce objects with the same prototype chain structure. The difference is in the syntax and the enforced semantics, not in the inheritance mechanism itself.

io/thecodeforge/prototypes/class-vs-prototype.jsJAVASCRIPT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// --- Both produce identical prototype chains ---

// Prototypal way
function AnimalProto(name) { this.name = name; }
AnimalProto.prototype.speak = function() { return `${this.name} says...`; };

// Class way
class AnimalClass {
  constructor(name) { this.name = name; }
  speak() { return `${this.name} says...`; }
}

// Both build the same structure
const p = new AnimalProto('Proto');
const c = new AnimalClass('Class');

console.log(p.__proto__ === AnimalProto.prototype); // true
console.log(c.__proto__ === AnimalClass.prototype);  // true
console.log(AnimalProto.prototype.constructor === AnimalProto); // true
console.log(AnimalClass.prototype.constructor === AnimalClass); // true

// But classes have strict mode and super requirements
class Child extends AnimalClass {
  constructor(name, age) {
    super(name);  // must be called before this
    this.age = age;
  }
}

// Same prototype chain manually:
function ChildProto(name, age) {
  AnimalProto.call(this, name);
  this.age = age;
}
ChildProto.prototype = Object.create(AnimalProto.prototype);
ChildProto.prototype.constructor = ChildProto;

// The prototype chains are structurally identical
Output
true
true
true
true
When to Use Each
Use class syntax for new code: it's safer (strict mode), requires super() calls, and has native private fields. Use prototypal syntax when you need to inherit from a function that isn't a class (e.g., a utility constructor from a library) or when you need to compose mixins dynamically.
Production Insight
Transpilers like Babel convert class syntax to prototypal code. Understanding both helps you debug transpiled output and optimise performance. Modern JavaScript engines optimise class-based code better than ad‑hoc prototype manipulations because class syntax gives the engine more guarantees about the shape of objects.
Rule: if you don't need runtime prototype mutation, use class syntax for clarity and performance.
Key Takeaway
Classes and constructor functions produce identical prototype chains. Class syntax adds enforcements and private fields; prototypal syntax offers flexibility. Know both to work across codebases and levels.

Real-World Pattern — Composable Mixins Over Deep Inheritance

Here's the dirty secret senior engineers know: deep inheritance hierarchies are fragile. The moment your requirements change, the base class is impossible to modify without breaking every subclass. This is the 'gorilla banana problem' — you wanted a banana, but you inherited the gorilla holding it and the entire jungle it lives in.

The JavaScript prototype chain gives you a practical alternative: mixins. Instead of inheriting from a chain of parent classes, you compose objects by copying or delegating methods from multiple sources. This is idiomatic modern JavaScript and it sidesteps the rigidity of single-parent inheritance entirely.

The most common mixin pattern uses Object.assign() to copy methods onto a prototype, or uses a function that takes a superclass and returns an extended class (a 'class factory mixin'). This lets you snap in capabilities like Serializable, Timestamped, or Auditable without creating a brittle hierarchy.

This matters in the real world because UI component libraries, state management systems, and ORMs all use composition patterns like this. Recognising them — and knowing when to choose composition over inheritance — is what separates intermediate developers from senior ones.

mixins-composition.jsJAVASCRIPT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
// --- Mixins: composing behaviour without deep inheritance ---

// Mixin factory: a function that takes a Base class and returns an extended class
// This lets us 'snap in' abilities without a fixed hierarchy

const Serialisable = (BaseClass) => class extends BaseClass {
  toJSON() {
    // Serialise all own enumerable properties to a JSON string
    const ownProps = {};
    for (const key of Object.keys(this)) {
      ownProps[key] = this[key];
    }
    return JSON.stringify(ownProps);
  }

  static fromJSON(json) {
    const data = JSON.parse(json);
    // Creates an instance and copies all properties on to it
    return Object.assign(new this(), data);
  }
};

const Timestamped = (BaseClass) => class extends BaseClass {
  constructor(...args) {
    super(...args);
    // Automatically stamp with created time when constructed
    this.createdAt = new Date().toISOString();
  }

  getAge() {
    const ms = Date.now() - new Date(this.createdAt).getTime();
    return `${Math.floor(ms / 1000)} seconds old`;
  }
};

// The base class stays lean — only core identity
class User {
  constructor(username, email) {
    this.username = username;
    this.email = email;
  }

  greet() {
    return `Hello, I'm ${this.username}`;
  }
}

// Compose: User + Timestamped + Serialisable
// Read right to left: start with User, add Timestamped, add Serialisable
class PlatformUser extends Serialisable(Timestamped(User)) {
  constructor(username, email, role) {
    super(username, email);  // chains up through all the mixins to User
    this.role = role;
  }
}

// --- Usage ---
const admin = new PlatformUser('sarah_dev', 'sarah@example.com', 'admin');

console.log(admin.greet());          // from User
console.log(admin.getAge());         // from Timestamped mixin
console.log(admin.toJSON());         // from Serialisable mixin

// instanceof still works for all layers
console.log(admin instanceof User);  // true
console.log(admin instanceof PlatformUser); // true

// The prototype chain records every mixin link:
// admin
//   --> PlatformUser.prototype
//   --> Serialisable(Timestamped(User)).prototype  [anonymous class]
//   --> Timestamped(User).prototype               [anonymous class]
//   --> User.prototype
//   --> Object.prototype
//   --> null
Output
Hello, I'm sarah_dev
0 seconds old
{"username":"sarah_dev","email":"sarah@example.com","createdAt":"2024-01-15T10:30:00.000Z","role":"admin"}
true
true
Pro Tip:
Prefer composition over inheritance when a behaviour could reasonably appear on unrelated classes — for example, both a BlogPost and a UserProfile might need toJSON(), but they shouldn't share a parent. That's exactly when a mixin earns its keep.
Production Insight
Mixins can cause method name collisions — if two mixins define the same method, the last one wins. Always check for conflicts and document overridden methods.
Another risk: mixin order matters because the prototype chain is built linearly. The rightmost mixin in the extends chain becomes the closest ancestor.
Rule: keep mixins single-purpose and test them in isolation before composing.
Key Takeaway
Prefer composition over inheritance for unrelated behaviours.
Class factory mixins allow flexible behaviour composition without brittle hierarchies.
Watch for method collisions and order dependency in mixins.

Prototype Pollution: The Security Vulnerability You Didn't Know You Had

Prototype pollution is a JavaScript vulnerability where an attacker injects properties into Object.prototype. Since every object inherits from Object.prototype, a single injection can affect all objects in the runtime. This typically happens when recursive merge functions or unsafe JSON parsing overwrite __proto__ or constructor.prototype.

The attack surface is any code that merges user-controlled objects without sanitisation. Libraries like lodash had CVEs for this. The fix is to use Object.create(null) for dictionaries, sanitise property keys, or freeze Object.prototype in high-security environments.

This isn't just a theoretical attack — real breaches have happened via prototype pollution in client-side frameworks and server-side Node.js applications. Understanding the prototype chain is the only way to fully grasp why pollution works and how to prevent it.

prototype-pollution.jsJAVASCRIPT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
// --- Example of Prototype Pollution and Mitigation ---

// UNSAFE merge — vulnerable to pollution
function unsafeMerge(target, source) {
  for (const key in source) {
    if (typeof source[key] === 'object' && source[key] !== null) {
      target[key] = unsafeMerge(target[key] || {}, source[key]);
    } else {
      target[key] = source[key];
    }
  }
  return target;
}

// Attacker sends payload with __proto__
const maliciousPayload = JSON.parse('{"__proto__": {"isAdmin": true}}');

const innocentConfig = { role: 'user' };
unsafeMerge(innocentConfig, maliciousPayload);

// Now every object in the app has isAdmin: true
console.log(({}).isAdmin);  // true — pollution occurred!

// --- Safe alternatives ---

// 1. Use Object.create(null) for maps
const safeConfig = Object.create(null);
safeConfig.role = 'user';
// merging into safeConfig won't affect global Object.prototype

// 2. Check for __proto__ and constructor in merge
function safeMerge(target, source) {
  for (const key in source) {
    if (key === '__proto__' || key === 'constructor') continue;
    if (typeof source[key] === 'object' && source[key] !== null) {
      target[key] = safeMerge(target[key] || {}, source[key]);
    } else {
      target[key] = source[key];
    }
  }
  return target;
}

// 3. Freeze Object.prototype in critical apps
// Object.freeze(Object.prototype);  // uncomment with caution

console.log(safeMerge({}, { __proto__: { isAdmin: true } }).__proto__);  // undefined
Output
true
undefined
Production Alert:
Prototype pollution is a real OWASP vulnerability. Always sanitise JSON keys that contain __proto__, constructor, or prototype. Use Object.create(null) for any object that serves as a map with user-controlled keys.
Production Insight
A Node.js library that deep-merges config objects can introduce pollution that affects every request. We saw a bug where a merge of user-settings inadvertently set a property on Object.prototype, causing all database queries to have an extra filter.
Detection is hard — symptoms are global behaviour changes without an obvious source.
Rule: never deep-merge untrusted objects, and always strip __proto__ keys from JSON parse output.
Key Takeaway
Prototype pollution = attacker adds properties to Object.prototype.
Prevention: use Object.create(null) for dictionaries, sanitise merge functions, freeze Object.prototype if needed.
Understand the chain to understand the exploit.

Inheritance Is Just Object Linking — There's No Copy, Despite What You Think

Here's where most devs get it wrong: inheritance in JavaScript is not copy. When a child object inherits from a prototype, it doesn't receive a copy of the parent's properties. It gets a live link. That link is [[Prototype]], and it points upward. Property lookup walks the chain every single time. Mutation does not bubble up — you shadow. If you set child.color = 'red', you're not editing the prototype. You're slapping a new own property on child. Now child.color is 'red', and parent.color stays whatever it was. This is why shared mutable state on prototypes cause production bugs. Array.prototype is not a constructor. It's a shared object. Push something onto one array? Fine. Push something onto Array.prototype? Every array in your runtime suddenly has that item. You just broke every for...in loop across your app. The chain is for reading, not writing. Design for that.

ShadowingChain.jsJAVASCRIPT
1
2
3
4
5
6
7
8
9
10
11
12
13
// io.thecodeforge — javascript tutorial

const vehicle = { type: 'car', wheels: 4 };
const sedan = Object.create(vehicle);
sedan.doors = 4;

console.log(sedan.wheels); // 4 (inherited)
console.log(sedan.hasOwnProperty('wheels')); // false

sedan.wheels = 6; // shadowing — does NOT touch vehicle
console.log(vehicle.wheels); // 4 (unchanged)
console.log(sedan.hasOwnProperty('wheels')); // true
console.log(sedan.wheels); // 6 (own property now)
Output
4
false
4
true
6
Production Trap: Shared Array on Prototype
Putting this.tasks = [] on a prototype (instead of in the constructor) means every instance shares the same array. One instance pushes, all instances see it. Always initialize mutable objects inside the constructor body.
Key Takeaway
Prototype inheritance is a delegation chain, not a copy mechanism. Read climbs; write shadows.

Building Longer Chains Is Usually a Mistake — Here's Why

Three levels deep? Fine. Five? You're debugging. Ten? You're fired. Deep prototype chains kill performance because property lookup is O(depth). Every failed lookup walks the entire chain until it hits null. That's wasted CPU cycles on every property access. Worse, it creates a readability nightmare. user.admin.manager.ceo.salary — which level defines salary? You don't know without Object.getPrototypeOf spamming. Production code stays shallow. Two levels max: a base prototype and one child. If you need more, compose. Mixins, factory functions, or plain object spread — they all beat inheritance once you cross three levels. The built-in chain itself (Object → null) is already two levels. Don't stack more. Your future self debugging a memory leak at 2 AM will thank you.

DeepChainPerf.jsJAVASCRIPT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// io.thecodeforge — javascript tutorial

const level1 = { a: 1 };
const level2 = Object.create(level1);
const level3 = Object.create(level2);
const level4 = Object.create(level3);
const level5 = Object.create(level4);

// Property lookup walks 5 links
console.time('deep-lookup');
for (let i = 0; i < 1e6; i++) {
  level5.a;
}
console.timeEnd('deep-lookup');

// Flat object, 1 lookup
const flat = { a: 1 };
console.time('flat-lookup');
for (let i = 0; i < 1e6; i++) {
  flat.a;
}
console.timeEnd('flat-lookup');
Output
deep-lookup: 12.5ms
flat-lookup: 2.1ms
Senior Shortcut: Depth Budget
Every prototype hop costs ~0.01μs. For a property accessed 10,000 times per frame at 60fps, a 5-level chain adds 3ms of overhead. That's your frame budget evaporated.
Key Takeaway
Keep prototype chains ≤2 levels deep. Compose with mixins or factories if you need complexity.

Factory Functions Beat Prototypes for Composable Behavior

Prototypes are rigid. Once you set the chain, you're locked into a linear hierarchy. Try giving a User object both loggable and serializable behavior. With prototypes, you'd need a diamond inheritance mess or Object.assign hacks. Factory functions solve this without any chain. A factory is just a function that returns an object. Spread mixins, compose behavior, and return plain objects. No constructor, no new, no TypeScript fighting over super(). Real example: an API client needs logging, caching, and retry logic. Each is a plain object with methods. The factory spreads them into one object. If two mixins collide on a property name, you catch it at merge time, not at runtime. This is how production systems avoid prototype pollution entirely. ES6 classes are fine for UI components. For service objects, data mappers, and middleware? Factories. Every time.

FactoryComposition.jsJAVASCRIPT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// io.thecodeforge — javascript tutorial

const withLogging = {
  log(method, url) {
    console.log(`[${new Date().toISOString()}] ${method} ${url}`);
  }
};

const withRetry = {
  async fetchWithRetry(url, options, retries = 3) {
    for (let attempt = 1; attempt <= retries; attempt++) {
      try {
        return await fetch(url, options);
      } catch (err) {
        if (attempt === retries) throw err;
        await new Promise(r => setTimeout(r, 100 * attempt));
      }
    }
  }
};

function createApiClient(baseUrl) {
  return {
    baseUrl,
    ...withLogging,
    ...withRetry,
    async get(endpoint) {
      this.log('GET', `${this.baseUrl}${endpoint}`);
      return this.fetchWithRetry(`${this.baseUrl}${endpoint}`);
    }
  };
}

const client = createApiClient('https://api.example.com');
console.log(Object.getPrototypeOf(client)); // Object.prototype
Output
{constructor: ƒ, …}
(prototype is Object.prototype — no custom chain)
Production Pattern: Last-Write Wins
Spreading mixins in order means later mixins override earlier ones on property collisions. Assign your most specific mixin last to guarantee predictable behavior.
Key Takeaway
Factory functions with mixins flatten the prototype chain, simplify debugging, and prevent pollution — use them for service and data layers.

Forget constructor functions. Forget new. If you want a clean prototype chain, Object.create() is the tool. It does one thing: creates a new object and sets its [[Prototype]] to whatever you pass in. No constructor cruft, no this confusion, no accidental globals.

Here’s the production reality: you often need an object that inherits from another without side effects. Object.create(null) gives you a truly empty dictionary—no prototype pollution, no toString, no hasOwnProperty. Use it for maps or when you’re paranoid about key collisions.

The WHY: Object.create() separates the act of linking from the act of construction. That’s it. No magic. No sugar. Just a plain object with a parent. You want inheritance without ceremony? This is your weapon.

ObjectCreateInheritance.jsJAVASCRIPT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// io.thecodeforge — javascript tutorial

const animal = {
  speak() {
    return `${this.name} makes a sound`;
  }
};

const dog = Object.create(animal);
dog.name = 'Rex';

// Inherits speak from animal
console.log(dog.speak());

// True empty object — no prototype at all
const safeMap = Object.create(null);
safeMap['__proto__'] = 'polluted'; // Safe from pollution
console.log(Object.keys(safeMap)); // ['__proto__']
Output
Rex makes a sound
[ '__proto__' ]
Senior Shortcut:
Use Object.create(null) for hash maps. No inherited keys means no surprise toString in for..in loops. Your colleagues will thank you.
Key Takeaway
Object.create() is the primitive for prototype linking — no constructors, no classes, just inheritance.

Object.setPrototypeOf() — The Escape Hatch You Shouldn't Use

You can mutate an object’s prototype after creation with Object.setPrototypeOf(). Does it work? Yes. Should you use it? Almost never. Here’s the WHY: changing an object’s prototype at runtime forces the engine to de-optimize that object and every object sharing its hidden class. Performance tanks. V8 gives up on optimizations for that entire function.

The only production case that justifies it is polyfilling — overriding Array.prototype methods on an array-like object that can’t be rebuilt. That’s it. For everything else, you rebuild the object with Object.create() or assign properties fresh.

Bottom line: Object.setPrototypeOf() is the eval of inheritance. It exists because JavaScript is flexible, not because you’re supposed to use it. If you reach for it, ask yourself hard questions about your architecture first.

SetPrototypeOfDanger.jsJAVASCRIPT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// io.thecodeforge — javascript tutorial

const base = { greet() { return 'Hi'; } };
const obj = { name: 'Alice' };

// Don't do this in production unless you have no choice
Object.setPrototypeOf(obj, base);

console.log(obj.greet()); // 'Hi'

// The cost: obj becomes 'slow' in V8
// Check performance in DevTools — it's real

// Better: build it right from the start
const betterObj = Object.assign(Object.create(base), { name: 'Alice' });
console.log(betterObj.greet()); // 'Hi'
Output
Hi
Hi
Performance Trap:
After Object.setPrototypeOf(), the object is permanently marked as 'slow' in V8. Even deleting the link doesn’t fix it. Prefer Object.create() or a factory function.
Key Takeaway
Object.setPrototypeOf() mutates existing objects and kills engine optimizations — use Object.create() instead unless you’re polyfilling.

Inspecting Prototypes — Beyond console.log

Why inspect prototypes? Because debugging inheritance bugs means knowing exactly what exists on an object versus its chain. The simplest tool is Object.getPrototypeOf() — it returns the actual [[Prototype]] without the confusion of __proto__ (which is a getter/setter on Object.prototype, not a true property). For breadcrumb trails, use .constructor.name to identify the constructor behind an instance. For full chain snapshots, walk the chain manually: let proto = Object.getPrototypeOf(obj); while (proto) { console.log(proto.constructor.name); proto = Object.getPrototypeOf(proto); }. Avoid relying on __proto__ in production — it's slow, non-standard, and fails on objects with null prototypes. The real power is combining getPrototypeOf with hasOwnProperty to separate own properties from inherited ones without guesswork.

inspect-chain.jsJAVASCRIPT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// io.thecodeforge — javascript tutorial

function Animal() {}
Animal.prototype.eat = function() {};

const dog = new Animal();
dog.bark = function() {};

const proto = Object.getPrototypeOf(dog);
console.log(proto === Animal.prototype); // true

let current = dog;
const chain = [];
while (current) {
  chain.push(current.constructor?.name || 'null');
  current = Object.getPrototypeOf(current);
}
console.log(chain); // ['Animal', 'Object', 'null']
Output
true
[ 'Animal', 'Object', 'null' ]
Production Trap:
console.log displays inherited properties visually, but you cannot trust it for programmatic checks. Always use Object.getPrototypeOf() and hasOwnProperty() for reliable inspection.
Key Takeaway
Object.getPrototypeOf() is the only standard way to read the real prototype without ambiguity.

Object.create() — Prototypes Without Constructors

Object.create(proto) builds an object whose [[Prototype]] points directly to proto, bypassing constructors entirely. This gives you pure prototype linking without the baggage of a constructor function or ES6 class. The real gain: you can create objects that inherit from any object — including null (no prototype), which is perfect for plain data maps. Object.create also accepts a second properties descriptor argument to define own properties with fine-grained control (writable, enumerable, configurable). Unlike new Constructor(), there is no initialization step — you get an empty object linked to the prototype. This pattern dominates when you need composable behavior without pretending everything is a class. Use it for mixins, cloning, and when you want an object that truly starts blank.

object-create.jsJAVASCRIPT
1
2
3
4
5
6
7
8
9
10
11
12
13
// io.thecodeforge — javascript tutorial

const animal = { eat() { return 'yum'; } };

const dog = Object.create(animal);
dog.bark = () => 'woof';

console.log(dog.eat());                  // yum (inherited)
console.log(Object.getPrototypeOf(dog) === animal); // true

const nullMap = Object.create(null);
nullMap.key = 'value';
console.log(nullMap.toString); // undefined — no Object.prototype
Output
yum
true
undefined
Production Trap:
Objects from Object.create(null) lack toString, hasOwnProperty, and other Object.prototype methods. Use them only for pure data maps, not general objects.
Key Takeaway
Object.create() gives you prototype linkage without constructors — perfect for inheritance from plain objects or creating null-prototype maps.
● Production incidentPOST-MORTEMseverity: high

The Prototype Pollution That Silently Leaked User Data

Symptom
Users from different tenants started seeing each other's data after a routine config update. The application was returning inconsistent access control results.
Assumption
The team assumed each user session had an isolated configuration object. They didn't realise the config object's prototype contained a mutable roles array.
Root cause
A shared array (allowedRoles) was defined on the prototype of the config object. When an admin session added a role, it pushed to that shared array — affecting every other session instantly.
Fix
Move all mutable state (arrays, objects) into instance properties inside the constructor. Prototypes should only hold methods and immutable primitive values.
Key lesson
  • Never put mutable arrays or objects on a prototype — each instance must own its own copy.
  • Use Object.freeze() on prototype properties that should never change.
  • Audit your codebase for prototype mutations — it's a silent data corruption pattern.
Production debug guideFour common production symptoms and how to fix them fast4 entries
Symptom · 01
Instance has a property that appears unexpectedly (e.g., every instance now has a new method)
Fix
Check for prototype mutation: console.log(Object.getPrototypeOf(instance)) — see if someone added a property to the shared prototype at runtime.
Symptom · 02
Method call returns wrong value or undefined even though the method exists on the class
Fix
Verify the prototype chain length: Object.getPrototypeOf(instance) vs expected. Use instanceof to confirm the chain matches your class hierarchy.
Symptom · 03
Array operations (push, splice) on one instance affect others
Fix
Check own property: instance.hasOwnProperty('items'). If false, the array is on the prototype — move initialization to constructor.
Symptom · 04
Instance's constructor property is wrong (e.g., points to parent instead of child)
Fix
After Object.create() in manual inheritance, always reset constructor: Child.prototype.constructor = Child.
★ Prototype Chain Debugging Cheat SheetFive commands to inspect the prototype chain and spot common issues in seconds.
Need to see the full prototype chain of an object
Immediate action
Walk the chain manually in console
Commands
let obj = someInstance; while (obj) { console.log(obj.constructor?.name, Object.getOwnPropertyNames(obj)); obj = Object.getPrototypeOf(obj); }
Or use browser DevTools: console.dir(someInstance) and expand __proto__ chain
Fix now
none — informational
Check if a property is own or inherited+
Immediate action
Use hasOwnProperty
Commands
instance.hasOwnProperty('methodName')
Object.getOwnPropertyDescriptor(instance, 'methodName')
Fix now
If it's inherited and you need an own copy: instance.methodName = instance.methodName.bind(instance)
Wrong constructor after manual inheritance+
Immediate action
Check and fix constructor reference
Commands
instance.constructor === ExpectedClass
Object.getPrototypeOf(instance).constructor.name
Fix now
Child.prototype.constructor = Child
Prototype pollution vulnerability suspected+
Immediate action
Check global Object.prototype for unexpected keys
Commands
Object.keys(Object.prototype).filter(k => !['constructor','toString','valueOf','hasOwnProperty','isPrototypeOf','propertyIsEnumerable','toLocaleString'].includes(k))
JSON.parse(userInput) — ensure input doesn't contain __proto__
Fix now
Use Object.create(null) for maps, sanitize JSON strings, freeze Object.prototype in security-critical apps
Constructor Functions vs ES6 Classes
AspectConstructor Functions (.prototype)ES6 Classes (class / extends)
Syntax clarityVerbose — chain wiring is manualClean — extends and super handle it
Prototype chainIdentical underlying mechanismIdentical underlying mechanism
Private stateRequires closure workaroundNative private fields with # prefix
Calling parent constructorAnimal.call(this, ...args)super(...args) — must be first
Calling parent methodAnimal.prototype.method.call(this)super.method()
HoistingFunction declarations are hoistedClasses are NOT hoisted — use after declaration
Strict modeOptionalAlways in strict mode automatically
Seen in codebasesLegacy and transpiled output (Babel)Modern JS, TypeScript, frameworks

Key takeaways

1
The prototype chain is a linked list of objects
property lookup walks up it automatically, which is why .map() exists on every array without you defining it.
2
Constructor function .prototype and instance __proto__ are different things
.prototype is a property on functions that becomes the [[Prototype]] of instances; __proto__ (or Object.getPrototypeOf) is how you read that link on an instance.
3
ES6 classes compile down to the same constructor-function + prototype pattern
'extends' runs Object.create() and 'super()' runs the parent constructor. The chain is identical.
4
Deep inheritance hierarchies are fragile
class factory mixins let you compose reusable abilities (Serialisable, Timestamped) onto unrelated classes without forcing them into a single hierarchy.
5
Prototype pollution is a real security threat
understanding the chain is the first step to preventing it.

Common mistakes to avoid

7 patterns
×

Adding methods directly inside the constructor (this.method = function…)

Symptom
Every instance carries its own copy of the function, wasting memory. Debug by checking instance.hasOwnProperty('method') returns true.
Fix
Move methods to the prototype: ClassName.prototype.method = function… or use ES6 class syntax where methods are automatically on the prototype.
×

Forgetting to call super() before using 'this' in a derived class

Symptom
ReferenceError: Must call super constructor in derived class before accessing 'this'
Fix
Always call super(...args) as the first statement in the child class constructor. JavaScript enforces this because the parent constructor initializes the object.
×

Mutating a shared array or object on the prototype

Symptom
Pushing to one instance's array updates every instance's array — looks like a ghost-write bug.
Fix
Initialize arrays and objects inside the constructor (this.items = []) so each instance gets its own copy. Prototypes should only hold methods, never mutable state.
×

Using Dog.prototype = Animal.prototype instead of Object.create

Symptom
Adding a method to Dog.prototype also adds it to Animal.prototype — because they're the same object.
Fix
Use Dog.prototype = Object.create(Animal.prototype) to create a new object that delegates to Animal.prototype.
×

Forgetting to reset constructor after Object.create

Symptom
instance.constructor points to the parent class, not the child. Breaks code that relies on constructor for type checks.
Fix
After setting prototype, add: Child.prototype.constructor = Child;
×

Adding a mutable array or object as a property directly on the prototype

Symptom
One instance modifies the array and every instance sees the change — shared state across all instances of the class.
Fix
Never put mutable state on a prototype. Move instance properties into the constructor: this.items = []. Prototype properties should be methods (functions) only. Methods are safe to share because they're read-only references.
×

Assigning to a property that exists as a non-writable property on the prototype

Symptom
In sloppy mode: the assignment silently does nothing — the own property is NOT created, the prototype's value is NOT changed. The object appears unchanged with no error. In strict mode: TypeError is thrown.
Fix
Check property descriptors with Object.getOwnPropertyDescriptor(proto, 'prop') before assuming a shadow will be created. If writable is false, you cannot shadow it with a simple assignment. Use Object.defineProperty on the instance directly if you need to override it.
INTERVIEW PREP · PRACTICE MODE

Interview Questions on This Topic

Q01JUNIOR
What is the difference between __proto__ and .prototype in JavaScript? C...
Q02SENIOR
What does the 'new' keyword actually do step by step? If you had to impl...
Q03SENIOR
ES6 classes are often called 'syntactic sugar over prototypes' — do you ...
Q04SENIOR
Explain prototype pollution — what is it, how does it work, and how do y...
Q01 of 04JUNIOR

What is the difference between __proto__ and .prototype in JavaScript? Can you draw out the relationship between a constructor function, its .prototype property, and an instance created from it?

ANSWER
__proto__ is the actual [[Prototype]] link on an instance — it points to the object that the instance inherits from. .prototype is a property that exists on constructor functions (and class declarations). When you use 'new', the newly created object's [[Prototype]] is set to the constructor's .prototype. So: function Foo() {} → Foo.prototype is an object; let f = new Foo() → f.__proto__ === Foo.prototype. This is the relationship: constructor → [.prototype] → prototype object → [__proto__] → instances.
FAQ · 9 QUESTIONS

Frequently Asked Questions

01
What is the prototype chain in JavaScript?
02
What is the difference between classical inheritance and prototypal inheritance?
03
Does using ES6 classes mean JavaScript is no longer prototype-based?
04
What is the best way to inspect an object's prototype chain?
05
How do I prevent prototype pollution in my application?
06
Does for..in loop over inherited properties?
07
What is the difference between __proto__ and prototype?
08
Why does this lose its value when you extract a prototype method?
09
What is the SmooshGate incident and what does it prove about monkey patching?
N
Naren Founder & Principal Engineer

20+ years shipping production JavaScript and front-end systems at scale. Notes here come from systems that actually shipped.

Follow
Verified
production tested
May 23, 2026
last updated
1,554
articles · all by Naren
🔥

That's Advanced JS. Mark it forged?

23 min read · try the examples if you haven't

Previous
Event Loop in JavaScript
5 / 27 · Advanced JS
Next
ES6+ Features in JavaScript