Core Concepts10 min read · Updated 2025-06-01

JavaScript Prototypes Explained: The Object Model Behind Every Class and Method

Classes didn't change JavaScript's object model — they just gave it a cleaner face. Understanding prototypes means understanding what actually runs when you call any method in JavaScript.

💡 Practice these concepts interactively with AI feedback

Start Practicing →

Here's a question most JavaScript developers can't answer precisely: when you call array.push(1), where does push come from? It's not on the array object itself. You didn't define it. Yet it works on every array, everywhere, with the correct behavior.

The answer is prototypes — the single mechanism that makes JavaScript's object system work. Classes didn't replace it. Classes are prototypes with better syntax.

Every Object Has a Prototype

Every JavaScript object (except objects explicitly created without one) has an internal [[Prototype]] slot — a reference to another object. This creates a chain. When you access a property on an object, JavaScript first looks at the object itself. If it's not there, it follows the [[Prototype]] link to the next object and looks there. It keeps following the chain until either the property is found or null is reached.

const obj = { name: 'Alice' }

// obj's [[Prototype]] is Object.prototype // Object.prototype's [[Prototype]] is null — end of the chain

obj.name // 'Alice' — found on obj directly obj.toString() // found on Object.prototype, not obj obj.missing // undefined — not found anywhere in the chain

This is the complete picture of property lookup in JavaScript. No special cases. Every property access in JavaScript follows this path.

The Actual Chain for Arrays

const arr = [1, 2, 3]

arr.push(4) // where is push? arr.map(x => x * 2) // where is map? arr.toString() // where is toString?

The chain: arr → Array.prototype → Object.prototype → null

When you create any array, it gets Array.prototype as its prototype — automatically. You don't have to do anything. The engine sets this up for you.

This is also why you can check Array.prototype:

Array.prototype.push   // the actual push function — same one every array uses
[].push === [].push    // true — same function reference, just found via prototype

One push function. Shared by every array in your program.

Constructor Functions: How Objects Got Prototypes Before Classes

Before ES6 classes, constructor functions were the standard way to create objects with shared behavior:

function User(name, email) {
  // These become own properties — stored on each instance
  this.name  = name
  this.email = email
}

// Methods go on the prototype — shared by all instances User.prototype.greet = function() { return Hi, I'm ${this.name} }

User.prototype.getEmailDomain = function() { return this.email.split('@')[1] }

const alice = new User('Alice', 'alice@example.com') const bob = new User('Bob', 'bob@example.com')

alice.greet() // "Hi, I'm Alice" bob.greet() // "Hi, I'm Bob" alice.greet === bob.greet // true — same function, shared via prototype

One greet function. Stored once. Used by every User instance.

What new Actually Does

The new keyword is doing four specific things. Knowing them makes new non-magical:

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

// What new User('Alice') does: // Step 1: Creates a new empty object const instance = {}

// Step 2: Sets its [[Prototype]] to User.prototype Object.setPrototypeOf(instance, User.prototype)

// Step 3: Calls User with 'this' bound to the new object User.call(instance, 'Alice')

// Step 4: Returns the instance (unless User explicitly returns a different object) // → instance is { name: 'Alice' }, linked to User.prototype

Implementing new manually is a classic interview question — it demonstrates you understand all four steps:

function myNew(Constructor, ...args) {
  const instance = Object.create(Constructor.prototype)
  const result   = Constructor.apply(instance, args)
  return result !== null && typeof result === 'object' ? result : instance
}

const alice = myNew(User, 'Alice') alice.greet() // "Hi, I'm Alice" — works identically

Classes: The Same Mechanism, Better Syntax

ES6 classes are syntactic sugar. Under the hood, they produce identical prototype chains to constructor functions. class is a keyword that writes the prototype wiring for you:

class User {
  constructor(name, email) {
    this.name  = name    // own property
    this.email = email   // own property
  }

greet() { // → User.prototype.greet return Hi, I'm ${this.name} }

getEmailDomain() { // → User.prototype.getEmailDomain return this.email.split('@')[1] }

static create(name, email) { // → User.create (on the constructor, not prototype) return new User(name, email) } }

// Proof that it's the same: typeof User // 'function' — a class IS a function User.prototype.greet // the greet method is on the prototype Object.getPrototypeOf(new User()) // User.prototype

Inheritance with classes:

class Admin extends User {
  constructor(name, email, permissions) {
    super(name, email)       // calls User's constructor — MUST come first
    this.permissions = permissions  // own property
  }

can(action) { // → Admin.prototype.can return this.permissions.includes(action) } }

const admin = new Admin('Charlie', 'charlie@corp.com', ['read', 'write'])

// Prototype chain: admin → Admin.prototype → User.prototype → Object.prototype → null admin.greet() // found on User.prototype via chain admin.can('read') // found on Admin.prototype admin instanceof Admin // true admin instanceof User // true — User.prototype is in the chain

extends sets up Admin.prototype's prototype to be User.prototype. That's all inheritance is — linking prototype chains.

Prototype vs Own Properties: The Distinction That Matters

const alice = new User('Alice', 'alice@example.com')

// Own properties — stored directly on alice alice.hasOwnProperty('name') // true alice.hasOwnProperty('email') // true

// Prototype properties — accessed via chain, not stored on alice alice.hasOwnProperty('greet') // false — greet is on User.prototype

// The 'in' operator checks the entire chain: 'name' in alice // true — own property 'greet' in alice // true — found on prototype 'push' in alice // false — not in this chain

// Object.keys — own enumerable only: Object.keys(alice) // ['name', 'email'] — prototype methods not listed

Why this matters: for...in iterates all enumerable properties, including inherited ones. If you've added anything to Object.prototype (a bad idea), it shows up in every for...in loop everywhere. Always use Object.keys() or Object.hasOwn(obj, key) to stay safe.

Object.create: Prototypal Inheritance Without Constructors

Object.create(proto) creates a new object with proto as its [[Prototype]] — no constructor needed:

const vehicle = {
  type: 'vehicle',
  describe() {
    return I am a ${this.type} named ${this.name}
  }
}

const car = Object.create(vehicle) car.type = 'car' car.name = 'Tesla'

car.describe() // 'I am a car named Tesla' — method from vehicle, data from car

// Object.create(null) — no prototype at all const dict = Object.create(null) dict.toString // undefined — genuinely no inherited properties // Safe as a key-value store when keys could conflict with Object.prototype methods

Prototype Pollution: Why This Knowledge Is a Security Concern

Since all plain objects share Object.prototype, modifying it affects everything:

// Never do this — but understand why libraries prevent it:
Object.prototype.isAdmin = true

const user = {} user.isAdmin // true — even an empty object now has this!

// This is prototype pollution — a real vulnerability class // Attack: user-controlled input like: // JSON.parse('{"__proto__": {"isAdmin": true}}') // when merged naively into an object, can inject properties onto Object.prototype

// Safe merge: const merged = Object.assign(Object.create(null), userInput) // or const merged = { ...userInput } // spread doesn't affect __proto__

The Interview Questions Prototypes Actually Appear In

"What is the difference between prototypal and classical inheritance?" Classical (C++, Java): objects are instances of classes; inheritance is a copy mechanism. Prototypal (JavaScript): objects inherit from other objects directly; properties are delegated via the live chain, not copied. Classes in JavaScript are syntax for prototypal inheritance, not a fundamentally different system.

"What does instanceof do?" Checks if Constructor.prototype exists anywhere in the object's prototype chain. Not whether the object was constructed by that constructor specifically — just whether the prototype appears in the chain. alice instanceof User is true if User.prototype is alice's prototype or any ancestor's prototype.

"How do you implement inheritance without classes?" Object.create(): const Child = Object.create(Parent). Constructor functions + Child.prototype = Object.create(Parent.prototype) + Child.prototype.constructor = Child. These produce identical chains to ES6 class inheritance.

"What's the difference between __proto__ and prototype?" prototype is a property on functions — it becomes the [[Prototype]] of objects created with new ThatFunction(). __proto__ is an accessor on objects that lets you read/write the [[Prototype]] slot. __proto__ is deprecated — use Object.getPrototypeOf() and Object.setPrototypeOf() instead.

📚 Practice These Topics
Classes
5–8 questions
Objects
8–12 questions
Prototype
6–10 questions
Event loop
6–10 questions

Put This Into Practice

Reading articles is passive. JSPrep Pro makes you actively recall, predict output, and get AI feedback.

Start Free →Browse All Questions

Related Articles

Core Concepts
map() vs forEach() in JavaScript: Which One to Use and Why It Matters
7 min read
Core Concepts
Arrow Functions vs Regular Functions in JavaScript: 6 Key Differences
9 min read
Interview Prep
Promise.all vs allSettled vs race vs any: The Complete Comparison
9 min read