Skip to main content

JavaScript Prototypes - Complete Guide

Table of Contentsโ€‹

  1. What is Prototype in JavaScript?
  2. __proto__ vs prototype
  3. The new Keyword - Internals Explained
  4. Prototype Chain - Lookup Mechanism
  5. Visual Prototype Chain
  6. Inheritance Using Constructor Functions
  7. Object.create() - Pure Prototypal Inheritance
  8. ES6 Classes - Syntactic Sugar
  9. Prototype vs Instance Members
  10. hasOwnProperty and in Operator
  11. Modifying Prototypes - Dangers
  12. __proto__ vs Object.getPrototypeOf()
  13. Built-in Prototype Chains
  14. Common Interview Traps
  15. Prototype Chain vs Scope Chain
  16. Object.create() vs new
  17. Performance of Prototype Lookup
  18. How instanceof Works Internally
  19. Prototype Pitfalls in Real Apps
  20. Advanced Interview Questions

What is Prototype in JavaScript?โ€‹

JavaScript uses prototypal inheritance, not classical inheritance like Java or C++.

Key Concept:โ€‹

Every object in JavaScript has a hidden internal property called [[Prototype]]. This property forms the prototype chain that enables inheritance.

Core Principle:โ€‹

Objects inherit properties and methods from other objects, not from classes.

Internal Property:โ€‹

const obj = {};
// obj has a hidden [[Prototype]] property
// You cannot directly access [[Prototype]]
// But you can access it via __proto__ or Object.getPrototypeOf()

__proto__ vs prototypeโ€‹

This is the MOST IMPORTANT and MOST CONFUSING concept in JavaScript prototypes.

__proto__ (Dunder Proto)โ€‹

  • Property on: Every object instance
  • Points to: The object's [[Prototype]]
  • Used for: Property lookup in the prototype chain
  • Type: Accessor property (getter/setter)
const obj = {};
console.log(obj.__proto__); // Object.prototype

prototypeโ€‹

  • Property on: Constructor functions only
  • Purpose: Blueprint for instances created with new
  • Used for: Defining shared methods and properties
  • Type: Regular object
function User() {}
User.prototype.sayHi = function() {
console.log('Hi!');
};

const user = new User();

The Golden Rule (Memorize This):โ€‹

object.__proto__ === Constructor.prototype

Visual Comparison:โ€‹

function User(name) {
this.name = name;
}

User.prototype.greet = function() {
console.log(`Hello, ${this.name}`);
};

const user1 = new User('Alice');

// The relationship:
user1.__proto__ === User.prototype // true

Relationship Diagram:โ€‹

Constructor Function (User)
|
| Has property
โ†“
User.prototype (object)
โ†‘
| Points to (__proto__)
|
user1 (instance)

The new Keyword - Internals Explainedโ€‹

Understanding what happens when you use new is crucial for understanding prototypes.

Example:โ€‹

function User(name) {
this.name = name;
}

const u = new User('Pawan');

What new Does Internally (4 Steps):โ€‹

  1. Creates an empty object: const obj = {}
  2. Sets prototype link: obj.__proto__ = User.prototype
  3. Executes constructor: User.call(obj, 'Pawan') (binds this to obj)
  4. Returns the object: Returns obj (unless constructor explicitly returns an object)

Manual Implementation of new:โ€‹

function myNew(Constructor, ...args) {
// Step 1: Create empty object
const obj = {};

// Step 2: Set prototype link
obj.__proto__ = Constructor.prototype;
// Or: Object.setPrototypeOf(obj, Constructor.prototype);

// Step 3: Execute constructor with obj as 'this'
const result = Constructor.apply(obj, args);

// Step 4: Return object (or constructor's return value if it's an object)
return typeof result === 'object' && result !== null ? result : obj;
}

// Usage:
function User(name) {
this.name = name;
}

const user = myNew(User, 'Alice');
console.log(user.name); // 'Alice'

Important Edge Case:โ€‹

function User() {
this.name = 'Alice';
return { custom: 'object' }; // Explicit object return
}

const u = new User();
console.log(u); // { custom: 'object' }
// The 'this.name' is ignored because constructor returns an object

Prototype Chain - Lookup Mechanismโ€‹

When you access a property on an object, JavaScript follows a specific lookup order.

Example:โ€‹

function User(name) {
this.name = name;
}

User.prototype.sayHi = function() {
console.log(`Hi, ${this.name}`);
};

const u = new User('Pawan');
u.sayHi(); // Where does JS find sayHi()?

Lookup Order:โ€‹

1. u (own properties) โŒ not found
โ†“
2. u.__proto__ (User.prototype) โœ… found sayHi

Full Chain Example:โ€‹

u.toString(); // Where is toString()?

Lookup order:

1. u (own properties) โŒ not found
โ†“
2. u.__proto__ (User.prototype) โŒ not found
โ†“
3. User.prototype.__proto__ (Object.prototype) โœ… found toString

When Property is Not Found:โ€‹

u.nonExistent; // undefined

// Chain:
u โ†’ u.__proto__ โ†’ Object.prototype โ†’ null
// Reaches null, returns undefined

Key Rules:โ€‹

  • Search stops at first match
  • Search ends at null
  • Returns undefined if not found

Visual Prototype Chainโ€‹

Understanding the visual structure helps tremendously in interviews.

Basic Chain:โ€‹

u (instance)
|
โ””โ”€โ”€ __proto__ โ†’ User.prototype
|
โ””โ”€โ”€ __proto__ โ†’ Object.prototype
|
โ””โ”€โ”€ __proto__ โ†’ null

Detailed Example:โ€‹

function Animal(name) {
this.name = name;
}

Animal.prototype.eat = function() {
console.log(`${this.name} is eating`);
};

function Dog(name, breed) {
Animal.call(this, name);
this.breed = breed;
}

Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;

Dog.prototype.bark = function() {
console.log('Woof!');
};

const dog = new Dog('Buddy', 'Golden Retriever');

Chain Visualization:โ€‹

dog
|
โ””โ”€โ”€ __proto__ โ†’ Dog.prototype
|
โ”œโ”€โ”€ bark() โœ…
โ”œโ”€โ”€ constructor: Dog
|
โ””โ”€โ”€ __proto__ โ†’ Animal.prototype
|
โ”œโ”€โ”€ eat() โœ…
|
โ””โ”€โ”€ __proto__ โ†’ Object.prototype
|
โ”œโ”€โ”€ toString() โœ…
โ”œโ”€โ”€ hasOwnProperty() โœ…
|
โ””โ”€โ”€ __proto__ โ†’ null

Inheritance Using Constructor Functionsโ€‹

This is the pre-ES6 way of implementing inheritance.

Complete Example:โ€‹

// Parent Constructor
function Animal(name) {
this.name = name;
}

Animal.prototype.speak = function() {
console.log(`${this.name} makes a sound`);
};

// Child Constructor
function Dog(name, breed) {
// Call parent constructor to inherit instance properties
Animal.call(this, name);
this.breed = breed;
}

// Set up prototype chain
Dog.prototype = Object.create(Animal.prototype);

// Fix constructor reference
Dog.prototype.constructor = Dog;

// Add child-specific methods
Dog.prototype.bark = function() {
console.log(`${this.name} barks`);
};

// Usage
const dog = new Dog('Tommy', 'Labrador');
dog.speak(); // 'Tommy makes a sound' (inherited)
dog.bark(); // 'Tommy barks' (own method)

Why Object.create()?โ€‹

// โŒ WRONG - Calls parent constructor
Dog.prototype = new Animal();

// โœ… CORRECT - Creates clean prototype link without calling constructor
Dog.prototype = Object.create(Animal.prototype);

Advantages of Object.create():

  • Creates a clean prototype link
  • Avoids calling the parent constructor
  • No unwanted instance properties on prototype

Why Fix constructor?โ€‹

Dog.prototype = Object.create(Animal.prototype);
console.log(Dog.prototype.constructor); // Animal โŒ

Dog.prototype.constructor = Dog;
console.log(Dog.prototype.constructor); // Dog โœ…

Importance:

  • Maintains proper constructor reference
  • Helps with debugging
  • Required for some reflection operations

Object.create() - Pure Prototypal Inheritanceโ€‹

Object.create() enables pure prototypal inheritance without constructor functions.

Syntax:โ€‹

Object.create(proto, [propertiesObject])

Basic Example:โ€‹

const parent = {
greet() {
console.log('Hello from parent');
}
};

const child = Object.create(parent);
child.greet(); // 'Hello from parent'

console.log(child.__proto__ === parent); // true

Creating Objects with Properties:โ€‹

const parent = {
greet() {
return `Hello, ${this.name}`;
}
};

const child = Object.create(parent, {
name: {
value: 'Alice',
writable: true,
enumerable: true,
configurable: true
}
});

console.log(child.greet()); // 'Hello, Alice'

Creating Object with No Prototype:โ€‹

const obj = Object.create(null);
console.log(obj.__proto__); // undefined
console.log(obj.toString); // undefined
// Useful for creating pure dictionaries/maps

ES6 Classes - Syntactic Sugarโ€‹

ES6 classes are just syntactic sugar over the prototype system.

Class Syntax:โ€‹

class Animal {
constructor(name) {
this.name = name;
}

speak() {
console.log(`${this.name} makes a sound`);
}
}

class Dog extends Animal {
constructor(name, breed) {
super(name); // Calls parent constructor
this.breed = breed;
}

bark() {
console.log(`${this.name} barks`);
}
}

const dog = new Dog('Max', 'Husky');
dog.speak(); // 'Max makes a sound'
dog.bark(); // 'Max barks'

What Happens Internally:โ€‹

// Under the hood, this is still:
Dog.prototype.__proto__ === Animal.prototype // true
dog.__proto__ === Dog.prototype // true

Class vs Constructor Function:โ€‹

FeatureClassConstructor Function
SyntaxCleanerMore verbose
HoistingNot hoistedHoisted
Strict modeAlways strictOptional
new requiredYes, throws errorNo, but recommended
PrototypeSame mechanismSame mechanism

Important: Classes are NOT Real Classesโ€‹

typeof Animal // 'function' โ—
Animal.prototype.constructor === Animal // true

JavaScript still uses prototypes under the hood!


Prototype vs Instance Membersโ€‹

Understanding where to place properties is crucial for memory efficiency.

Instance Properties:โ€‹

function User(name) {
this.name = name; // Instance property (unique per object)
this.greet = function() { // Instance method (duplicated!)
console.log(`Hi, ${this.name}`);
};
}

const u1 = new User('Alice');
const u2 = new User('Bob');

console.log(u1.greet === u2.greet); // false โŒ Different functions!

Prototype Properties:โ€‹

function User(name) {
this.name = name; // Instance property (unique per object)
}

User.prototype.greet = function() { // Shared method โœ…
console.log(`Hi, ${this.name}`);
};

const u1 = new User('Alice');
const u2 = new User('Bob');

console.log(u1.greet === u2.greet); // true โœ… Same function!

Memory Comparison:โ€‹

// โŒ BAD - 1000 function copies in memory
for (let i = 0; i < 1000; i++) {
const user = new User('User' + i);
// Each has its own greet function
}

// โœ… GOOD - 1 shared function in memory
User.prototype.greet = function() { /*...*/ };
for (let i = 0; i < 1000; i++) {
const user = new User('User' + i);
// All share the same greet function
}

Best Practice:โ€‹

Instance properties โ†’ Constructor
Shared methods โ†’ Prototype

hasOwnProperty and in Operatorโ€‹

These methods help distinguish between own and inherited properties.

hasOwnProperty():โ€‹

Checks if property exists directly on the object (not in prototype chain).

function User(name) {
this.name = name;
}

User.prototype.greet = function() {};

const user = new User('Alice');

console.log(user.hasOwnProperty('name')); // true โœ…
console.log(user.hasOwnProperty('greet')); // false โŒ (in prototype)
console.log(user.hasOwnProperty('toString')); // false โŒ

in Operator:โ€‹

Checks if property exists anywhere in the prototype chain.

console.log('name' in user);     // true โœ…
console.log('greet' in user); // true โœ… (in prototype)
console.log('toString' in user); // true โœ… (in Object.prototype)
console.log('fake' in user); // false โŒ

Comparison Table:โ€‹

MethodOwn PropertiesInherited Properties
hasOwnProperty()โœ… YesโŒ No
in operatorโœ… Yesโœ… Yes

Iterating Own Properties:โ€‹

const user = new User('Alice');

// Only own enumerable properties
for (let key in user) {
if (user.hasOwnProperty(key)) {
console.log(key, user[key]);
}
}

// Modern approach
Object.keys(user); // ['name'] - only own enumerable
Object.getOwnPropertyNames(user); // ['name'] - all own properties

Modifying Prototypes - Dangersโ€‹

Modifying prototypes affects all existing and future instances.

Example:โ€‹

function User(name) {
this.name = name;
}

const u1 = new User('Alice');
const u2 = new User('Bob');

// Modify prototype AFTER instances created
User.prototype.age = 30;

console.log(u1.age); // 30 โš ๏ธ Both affected!
console.log(u2.age); // 30 โš ๏ธ

Dangers:โ€‹

// โŒ DANGEROUS - Modifying built-in prototypes
Array.prototype.first = function() {
return this[0];
};

[1, 2, 3].first(); // 1

// Problems:
// 1. Affects ALL arrays globally
// 2. Can break third-party libraries
// 3. Future JS versions might add same method
// 4. Pollutes for...in loops

Safe Approach:โ€‹

// โœ… GOOD - Use utility functions instead
function first(arr) {
return arr[0];
}

first([1, 2, 3]); // 1

When Prototype Modification is OK:โ€‹

// Polyfills for older browsers
if (!Array.prototype.includes) {
Array.prototype.includes = function(searchElement) {
return this.indexOf(searchElement) !== -1;
};
}

__proto__ vs Object.getPrototypeOf()โ€‹

While __proto__ works, it's not the recommended way to access prototypes.

__proto__ (Deprecated Pattern):โ€‹

const obj = {};
console.log(obj.__proto__); // Object.prototype

// Setting prototype
const parent = { x: 10 };
const child = {};
child.__proto__ = parent;

Issues:

  • Not part of ES6 standard (added for compatibility)
  • Performance issues
  • Deprecated in favor of modern methods
// Get prototype
const proto = Object.getPrototypeOf(obj);

// Set prototype
const parent = { x: 10 };
const child = {};
Object.setPrototypeOf(child, parent);

// Or better: use Object.create()
const child = Object.create(parent);

Performance Note:โ€‹

// โŒ BAD - Changing prototype after creation is SLOW
const obj = {};
Object.setPrototypeOf(obj, parent);

// โœ… GOOD - Set prototype during creation
const obj = Object.create(parent);

Comparison Table:โ€‹

MethodStatusUse Case
__proto__DeprecatedAvoid in production
Object.getPrototypeOf()โœ… RecommendedReading prototype
Object.setPrototypeOf()โš ๏ธ Use carefullyChanging prototype (slow)
Object.create()โœ… RecommendedCreating with prototype

Built-in Prototype Chainsโ€‹

Understanding built-in prototype chains helps with debugging and advanced usage.

Array Prototype Chain:โ€‹

const arr = [1, 2, 3];

arr
โ””โ”€โ”€ __proto__ โ†’ Array.prototype
|
โ”œโ”€โ”€ map() โœ…
โ”œโ”€โ”€ filter() โœ…
โ”œโ”€โ”€ push() โœ…
|
โ””โ”€โ”€ __proto__ โ†’ Object.prototype
|
โ”œโ”€โ”€ toString() โœ…
โ””โ”€โ”€ __proto__ โ†’ null
console.log(arr.__proto__ === Array.prototype); // true
console.log(Array.prototype.__proto__ === Object.prototype); // true
console.log(Object.prototype.__proto__); // null

Function Prototype Chain:โ€‹

function myFunc() {}

myFunc
โ””โ”€โ”€ __proto__ โ†’ Function.prototype
|
โ”œโ”€โ”€ call() โœ…
โ”œโ”€โ”€ apply() โœ…
โ”œโ”€โ”€ bind() โœ…
|
โ””โ”€โ”€ __proto__ โ†’ Object.prototype
|
โ””โ”€โ”€ __proto__ โ†’ null

String Prototype Chain:โ€‹

const str = "hello";

str (primitive)
|
โ””โ”€โ”€ Wrapped temporarily as String object
|
โ””โ”€โ”€ __proto__ โ†’ String.prototype
|
โ”œโ”€โ”€ toUpperCase() โœ…
โ”œโ”€โ”€ slice() โœ…
|
โ””โ”€โ”€ __proto__ โ†’ Object.prototype

All Built-in Objects Eventually Lead to Object.prototype:โ€‹

Any Object โ†’ SpecificType.prototype โ†’ Object.prototype โ†’ null

Common Interview Trapsโ€‹

Trap 1: Arrow Functions and Prototypeโ€‹

const User = (name) => {
this.name = name;
};

User.prototype.greet = function() {}; // undefined!

const u = new User('Alice'); // โŒ TypeError: User is not a constructor

Why?

  • Arrow functions don't have prototype property
  • Arrow functions cannot be used with new
  • Arrow functions don't have their own this

Trap 2: Changing Prototype After Object Creationโ€‹

function User() {}
const u1 = new User();

// Change prototype
User.prototype = { newMethod() {} };

const u2 = new User();

console.log(u1.newMethod); // undefined โŒ (points to old prototype)
console.log(u2.newMethod); // function โœ… (points to new prototype)

Lesson: Existing instances maintain reference to old prototype.

Trap 3: Why Methods Go on Prototype?โ€‹

// โŒ BAD - Creates function for each instance
function User(name) {
this.greet = function() {
console.log(name);
};
}

// 1000 instances = 1000 function copies โŒ

// โœ… GOOD - Shared method
User.prototype.greet = function() {
console.log(this.name);
};

// 1000 instances = 1 function โœ…

Benefits:

  • Memory efficiency - shared across all instances
  • Faster object creation - less work per instance
  • Easier updates - modify once, affects all instances

Trap 4: Forgetting constructor Fixโ€‹

function Parent() {}
function Child() {}

Child.prototype = Object.create(Parent.prototype);
// Forgot: Child.prototype.constructor = Child;

const c = new Child();
console.log(c.constructor); // Parent โŒ Wrong!
console.log(c.constructor === Child); // false โŒ

Trap 5: Prototype Property Shadowingโ€‹

User.prototype.role = 'user';

const u1 = new User();
console.log(u1.role); // 'user' (from prototype)

u1.role = 'admin'; // Creates OWN property
console.log(u1.role); // 'admin' (own property shadows prototype)

delete u1.role;
console.log(u1.role); // 'user' (back to prototype)

Prototype Chain vs Scope Chainโ€‹

These are completely different concepts that students often confuse.

AspectPrototype ChainScope Chain
PurposeProperty/method lookupVariable lookup
CreatedRuntime (object creation)Definition time (lexical)
Basis__proto__ linksNested functions
Lookupthis-basedLexical scope
Related toObjects and inheritanceFunctions and closures
Ends atnullGlobal scope

Example Comparison:โ€‹

const parent = {
x: 10,
getX() {
return this.x;
}
};

const child = Object.create(parent);
child.x = 20;

function outer() {
const x = 30;

function inner() {
console.log(x); // 30 (scope chain)
console.log(child.getX()); // 20 (prototype chain + this)
}

inner();
}

outer();

Explanation:

  • x in inner() โ†’ scope chain lookup โ†’ finds 30 in outer()
  • child.getX() โ†’ prototype chain lookup โ†’ finds method in parent
  • this.x inside getX() โ†’ this refers to child, so returns 20

Object.create() vs newโ€‹

Both create objects but work differently.

Using new:โ€‹

function User(name) {
this.name = name;
}

User.prototype.greet = function() {
console.log(`Hi, ${this.name}`);
};

const user = new User('Alice');

What happens:

  1. Creates object
  2. Links to User.prototype
  3. Runs constructor with passed arguments
  4. Returns object

Using Object.create():โ€‹

const userPrototype = {
greet() {
console.log(`Hi, ${this.name}`);
}
};

const user = Object.create(userPrototype);
user.name = 'Alice'; // Manual assignment

What happens:

  1. Creates object
  2. Links to specified prototype
  3. Does NOT run any constructor
  4. Returns object

Comparison Table:โ€‹

Featurenew Constructor()Object.create(proto)
Runs constructorโœ… YesโŒ No
Sets prototypeโœ… Yes (Constructor.prototype)โœ… Yes (specified proto)
Passes argumentsโœ… YesโŒ No
Use caseConstructor patternPure prototypal inheritance
InitializationAutomaticManual

When to Use Each:โ€‹

Use new:

  • When you have a constructor function
  • When you need initialization logic
  • When following constructor pattern

Use Object.create():

  • When you want pure prototypal inheritance
  • When setting up inheritance chains
  • When you need an object with specific prototype

Combining Both:โ€‹

function Animal(name) {
this.name = name;
}

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

function Dog(name) {
Animal.call(this, name); // Borrow constructor
}

Dog.prototype = Object.create(Animal.prototype); // Set prototype
Dog.prototype.constructor = Dog;

const dog = new Dog('Max'); // Use new for final object

Performance of Prototype Lookupโ€‹

Prototype lookup has performance implications in real applications.

Lookup Cost:โ€‹

const obj = {
a: 1
};

// Fast - own property (1 lookup)
console.log(obj.a);

// Slower - prototype property (2 lookups)
console.log(obj.toString);

// Even slower - deep in chain (3+ lookups)
obj.__proto__.__proto__.hasOwnProperty;

Lookup Speed:โ€‹

Own property:     FAST โšก (1 lookup)
Prototype: MEDIUM ๐Ÿข (2 lookups)
Deep prototype: SLOW ๐ŸŒ (3+ lookups)

Performance Best Practices:โ€‹

1. Cache Prototype Properties:โ€‹

// โŒ SLOW - Repeated prototype lookups
for (let i = 0; i < 1000000; i++) {
obj.toString(); // Looks up prototype each time
}

// โœ… FAST - Cache the method
const toString = obj.toString;
for (let i = 0; i < 1000000; i++) {
toString.call(obj);
}

2. Avoid Deep Prototype Chains:โ€‹

// โŒ BAD - 5 levels deep
A โ†’ B โ†’ C โ†’ D โ†’ E โ†’ Object.prototype โ†’ null

// โœ… GOOD - 2 levels deep
Child โ†’ Parent โ†’ Object.prototype โ†’ null

3. Use Own Properties for Frequently Accessed Data:โ€‹

// โŒ SLOW - Method accessed millions of times
Class.prototype.criticalMethod = function() {};

// โœ… FAST - Copy to instance if called frequently
this.criticalMethod = Class.prototype.criticalMethod;

Modern Engines Optimize:โ€‹

Modern JavaScript engines (V8, SpiderMonkey) use:

  • Inline caching - remembers lookup results
  • Hidden classes - optimizes property access
  • JIT compilation - compiles hot paths

But deep chains still have overhead!


How instanceof Works Internallyโ€‹

Understanding instanceof helps debug inheritance issues.

Syntax:โ€‹

object instanceof Constructor

What It Checks:โ€‹

Does Constructor.prototype exist anywhere in object's prototype chain?

Manual Implementation:โ€‹

function myInstanceof(obj, Constructor) {
// Get the prototype of the object
let proto = Object.getPrototypeOf(obj);

// Get the prototype property of the constructor
const prototype = Constructor.prototype;

// Walk up the prototype chain
while (proto !== null) {
if (proto === prototype) {
return true;
}
proto = Object.getPrototypeOf(proto);
}

return false;
}

Examples:โ€‹

function Animal() {}
function Dog() {}

Dog.prototype = Object.create(Animal.prototype);

const dog = new Dog();

console.log(dog instanceof Dog); // true โœ…
console.log(dog instanceof Animal); // true โœ…
console.log(dog instanceof Object); // true โœ…
console.log(dog instanceof Array); // false โŒ

How It Works:โ€‹

dog instanceof Dog:
dog.__proto__ === Dog.prototype? YES โœ…

dog instanceof Animal:
dog.__proto__ === Animal.prototype? NO
dog.__proto__.__proto__ === Animal.prototype? YES โœ…

dog instanceof Object:
dog.__proto__ === Object.prototype? NO
dog.__proto__.__proto__ === Object.prototype? NO
dog.__proto__.__proto__.__proto__ === Object.prototype? YES โœ…

Gotcha - Changing Prototype:โ€‹

function User() {}
const user = new User();

console.log(user instanceof User); // true โœ…

// Change prototype
User.prototype = {};

console.log(user instanceof User); // false โŒ (different prototype now!)

Cross-Frame Issues:โ€‹

// iframe has its own Array constructor
const iframe = document.createElement('iframe');
document.body.appendChild(iframe);
const iframeArray = iframe.contentWindow.Array;

const arr = new iframeArray();
console.log(arr instanceof Array); // false โŒ (different Array)
console.log(Array.isArray(arr)); // true โœ… (reliable check)

Lesson: Use Array.isArray() for arrays, not instanceof.


Prototype Pitfalls in Real Appsโ€‹

Real-world problems you'll encounter with prototypes.

Pitfall 1: Shared Mutable Stateโ€‹

function User() {}

User.prototype.friends = []; // โŒ Shared array!

const u1 = new User();
const u2 = new User();

u1.friends.push('Alice');

console.log(u2.friends); // ['Alice'] โš ๏ธ Affected!

Solution:

function User() {
this.friends = []; // โœ… Instance property
}

Pitfall 2: Modifying Built-in Prototypes in Librariesโ€‹

// Library A
Array.prototype.remove = function(item) {
const index = this.indexOf(item);
if (index > -1) this.splice(index, 1);
};

// Library B (conflicts!)
Array.prototype.remove = function(index) {
this.splice(index, 1);
};

// Your code - which remove() runs? ๐Ÿ˜ฑ

Solution: Never modify built-in prototypes in libraries.

Pitfall 3: Memory Leaks with Closuresโ€‹

function User(name) {
const data = new Array(1000000); // Large data

this.getName = function() {
return name; // Closure keeps entire 'data' in memory! โŒ
};
}

// โœ… Better - no closure, no leak
function User(name) {
this.name = name;
}

User.prototype.getName = function() {
return this.name;
};

Pitfall 4: Incorrect Inheritance Setupโ€‹

// โŒ WRONG - Calls parent constructor during setup
function Child() {}
Child.prototype = new Parent();

// โœ… CORRECT
Child.prototype = Object.create(Parent.prototype);
Child.prototype.constructor = Child;

Pitfall 5: Forgetting this Contextโ€‹

function User(name) {
this.name = name;
}

User.prototype.greet = function() {
console.log(this.name);
};

const user = new User('Alice');
const greet = user.greet;

greet(); // undefined โŒ (lost 'this' context)

// โœ… Solutions:
greet.call(user); // Use call/apply
user.greet.bind(user)(); // Bind this
() => user.greet(); // Arrow function wrapper

Advanced Interview Questionsโ€‹

Question 1: Explain the outputโ€‹

function User() {}
User.prototype.name = 'Alice';

const u1 = new User();
const u2 = new User();

u1.name = 'Bob';

console.log(u1.name);
console.log(u2.name);
console.log(User.prototype.name);

delete u1.name;

console.log(u1.name);
Click for answer

Output:

Bob
Alice
Alice
Alice

Explanation:

  1. u1.name = 'Bob' creates an own property on u1 (shadows prototype)
  2. u2.name still reads from prototype โ†’ 'Alice'
  3. User.prototype.name unchanged โ†’ 'Alice'
  4. After delete u1.name, own property removed, falls back to prototype โ†’ 'Alice'

Question 2: What's wrong with this code?โ€‹

function Animal() {}
Animal.prototype = {
speak: function() {
console.log('sound');
}
};

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

Dog.prototype.bark = function() {
console.log('woof');
};

const animal = new Animal();
animal.bark(); // What happens?
Click for answer

Output: 'woof'

Problem: Dog.prototype = Animal.prototype creates a reference, not a copy!

Why it's bad:

Dog.prototype === Animal.prototype // true โŒ

// When you add bark to Dog.prototype,
// you're also adding it to Animal.prototype!

Fix:

Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;

Question 3: Multiple Inheritance Simulationโ€‹

// Can we inherit from multiple prototypes?
function CanEat() {}
CanEat.prototype.eat = function() { console.log('eating'); };

function CanWalk() {}
CanWalk.prototype.walk = function() { console.log('walking'); };

function Animal() {}

// How to make Animal inherit from both?
Click for answer

JavaScript doesn't support multiple inheritance directly, but we can simulate it:

// Solution 1: Mixins
Object.assign(Animal.prototype, CanEat.prototype, CanWalk.prototype);

const animal = new Animal();
animal.eat(); // 'eating'
animal.walk(); // 'walking'

// Solution 2: Manual copying
function mixin(target, ...sources) {
sources.forEach(source => {
Object.getOwnPropertyNames(source.prototype).forEach(name => {
if (name !== 'constructor') {
target.prototype[name] = source.prototype[name];
}
});
});
}

mixin(Animal, CanEat, CanWalk);

Note: These are shallow copies, not true inheritance chains.

Question 4: Explain new Behaviorโ€‹

function User(name) {
this.name = name;

return { custom: 'object' };
}

const u1 = new User('Alice');
console.log(u1.name);
console.log(u1.custom);

function Admin(name) {
this.name = name;

return 42; // primitive
}

const a1 = new Admin('Bob');
console.log(a1.name);
Click for answer

Output:

undefined
object
Bob

Explanation:

  1. If constructor returns an object, that object is returned (ignoring this)
  2. If constructor returns a primitive or nothing, the newly created object is returned
// User case:
// return { custom: 'object' }; โ†’ returns this object
// u1 = { custom: 'object' }
// u1.name is undefined

// Admin case:
// return 42; โ†’ primitive, ignored
// a1 = { name: 'Bob' } (the object created by new)

Question 5: Prototype Chain Lengthโ€‹

function A() {}
function B() {}
function C() {}

B.prototype = Object.create(A.prototype);
C.prototype = Object.create(B.prototype);

const c = new C();

// How many steps to reach null?
// c โ†’ ? โ†’ ? โ†’ ? โ†’ null
Click for answer

Answer: 4 steps

c
โ†’ C.prototype
โ†’ B.prototype
โ†’ A.prototype
โ†’ Object.prototype
โ†’ null

Code to verify:

let proto = c;
let count = 0;

while ((proto = Object.getPrototypeOf(proto)) !== null) {
count++;
console.log(`Step ${count}:`, proto.constructor?.name || 'Object');
}

console.log(`Total steps: ${count}`); // 4

Killer Interview One-Linersโ€‹

Core Concept:โ€‹

"JavaScript objects inherit from other objects via prototype chains, where __proto__ links instances to constructor prototypes, and new wires this chain during object creation."

__proto__ vs prototype:โ€‹

"__proto__ is the actual link used for lookup on instances, while prototype is the blueprint property on constructors that __proto__ points to."

Inheritance:โ€‹

"Prototypal inheritance creates a chain where property lookup cascades through __proto__ links until the property is found or null is reached."

Memory Efficiency:โ€‹

"Prototype methods are shared across all instances for memory efficiency, while instance properties are unique per object."


Key Takeawaysโ€‹

  1. โœ… Every object has __proto__ (link), only constructor functions have prototype (blueprint)
  2. โœ… object.__proto__ === Constructor.prototype is the fundamental relationship
  3. โœ… new creates objects, links prototypes, runs constructors, and returns the result
  4. โœ… Prototype chain enables inheritance through __proto__ links until null
  5. โœ… Methods on prototype are shared (memory efficient), properties on this are unique
  6. โœ… Object.create() creates pure prototypal inheritance without constructors
  7. โœ… ES6 classes are syntactic sugar over the prototype system
  8. โœ… Never modify built-in prototypes in production code
  9. โœ… Use Object.getPrototypeOf() instead of __proto__ in modern code
  10. โœ… Deep prototype chains have performance costs

Visual Summaryโ€‹

Complete Prototype System:โ€‹

Constructor Function
|
| has property 'prototype'
โ†“
Constructor.prototype (object)
|
| contains shared methods
|
โ†‘ __proto__ points to
|
Instance (created with 'new')
|
| has own properties
|
| can access prototype methods via chain
|
โ””โ†’ lookup: own props โ†’ prototype โ†’ Object.prototype โ†’ null

The Triangle Relationship:โ€‹

        Constructor
|
has ___โ†“___ references
/ \
prototype instance
\ /
\__โ†—__/
__proto__

Understanding prototypes is fundamental to mastering JavaScript inheritance, performance optimization, and advanced patterns!