Skip to main content
How does a plain JavaScript object know about methods like .toString() or .hasOwnProperty() that you never defined? How does JavaScript let objects inherit from other objects without traditional classes?
// You create a simple object
const player = { name: "Alice", health: 100 }

// But it has methods you never defined!
console.log(player.toString())        // "[object Object]"
console.log(player.hasOwnProperty("name"))  // true

// Where do these come from?
console.log(Object.getPrototypeOf(player))  // { constructor: Object, toString: f, ... }
The answer is the prototype chain. It’s JavaScript’s inheritance mechanism, defined in the ECMAScript specification as the [[Prototype]] internal slot. Every object has a hidden link to another object called its prototype. When you access a property, JavaScript looks for it on the object first, then follows this chain of prototypes until it finds the property or reaches the end (null).
What you’ll learn in this guide:
  • What the prototype chain is and how property lookup works
  • The difference between [[Prototype]], __proto__, and .prototype
  • How to create objects with Object.create()
  • What the new operator does (the 4 steps)
  • How to copy properties with Object.assign()
  • How to inspect and modify prototypes
  • Common prototype methods like hasOwnProperty()
  • Prototype pitfalls and how to avoid them
Prerequisites: This guide assumes you understand Primitive Types and Primitives vs Objects. If objects and their properties are new to you, read those guides first!

What is the Prototype Chain?

The prototype chain is JavaScript’s way of implementing inheritance. Every object has an internal link (called [[Prototype]]) to another object, its prototype. When you try to access a property on an object, JavaScript:
  1. First looks for the property on the object itself
  2. If not found, looks on the object’s prototype
  3. If still not found, looks on the prototype’s prototype
  4. Continues until it finds the property or reaches null (the end of the chain)
// Create a simple object
const wizard = {
  name: "Gandalf",
  castSpell() {
    return `${this.name} casts a spell!`
  }
}

// Create another object that inherits from wizard
const apprentice = Object.create(wizard)
apprentice.name = "Harry"

// apprentice has its own 'name' property
console.log(apprentice.name)  // "Harry"

// But castSpell comes from the prototype (wizard)
console.log(apprentice.castSpell())  // "Harry casts a spell!"

// The prototype chain:
// apprentice → wizard → Object.prototype → null
┌─────────────────────────────────────────────────────────────────────────┐
│                         THE PROTOTYPE CHAIN                              │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                          │
│   apprentice.castSpell()                                                 │
│        │                                                                 │
│        │  1. Does apprentice have castSpell? NO                          │
│        ▼                                                                 │
│   ┌──────────────┐                                                       │
│   │  apprentice  │                                                       │
│   │──────────────│                                                       │
│   │ name: "Harry"│                                                       │
│   │ [[Prototype]]│────┐                                                  │
│   └──────────────┘    │                                                  │
│                       │  2. Does wizard have castSpell? YES! Use it      │
│                       ▼                                                  │
│               ┌──────────────────┐                                       │
│               │      wizard      │                                       │
│               │──────────────────│                                       │
│               │ name: "Gandalf"  │                                       │
│               │ castSpell: fn    │ ◄── Found here!                       │
│               │ [[Prototype]]    │────┐                                  │
│               └──────────────────┘    │                                  │
│                                       │  3. If not found, keep going...  │
│                                       ▼                                  │
│                           ┌────────────────────┐                         │
│                           │  Object.prototype  │                         │
│                           │────────────────────│                         │
│                           │ toString: fn       │                         │
│                           │ hasOwnProperty: fn │                         │
│                           │ [[Prototype]]      │────┐                    │
│                           └────────────────────┘    │                    │
│                                                     │                    │
│                                                     ▼                    │
│                                                   null                   │
│                                            (end of chain)                │
│                                                                          │
└─────────────────────────────────────────────────────────────────────────┘
The Chain Always Ends: Every prototype chain eventually reaches Object.prototype, then null. As documented on MDN, this is why all objects have access to methods like toString() and hasOwnProperty(). They inherit them from Object.prototype.

Understanding [[Prototype]], __proto__, and .prototype

These three terms confuse many developers. Let’s clarify:
TermWhat It IsHow to Access
[[Prototype]]The internal prototype link every object hasNot directly accessible (it’s internal)
__proto__A getter/setter that exposes [[Prototype]]obj.__proto__ (deprecated, avoid in production)
.prototypeA property on functions used when creating instances with newFunction.prototype
// Every object has [[Prototype]] — an internal link to its prototype
const player = { name: "Alice" }

// __proto__ exposes [[Prototype]] (deprecated but works)
console.log(player.__proto__ === Object.prototype)  // true

// .prototype exists only on FUNCTIONS
function Player(name) {
  this.name = name
}

// When you use 'new Player()', the new object's [[Prototype]]
// is set to Player.prototype
console.log(Player.prototype)  // { constructor: Player }

const alice = new Player("Alice")
console.log(Object.getPrototypeOf(alice) === Player.prototype)  // true
┌─────────────────────────────────────────────────────────────────────────┐
│                    THE THREE PROTOTYPE TERMS                             │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                          │
│  [[Prototype]]     The hidden internal slot every object has             │
│  ──────────────    Points to the object's prototype                      │
│                    You can't access it directly                          │
│                                                                          │
│  __proto__         A way to READ/WRITE [[Prototype]]                     │
│  ─────────         obj.__proto__ = Object.getPrototypeOf(obj)            │
│                    DEPRECATED! Use Object.getPrototypeOf() instead       │
│                                                                          │
│  .prototype        A property that exists ONLY on functions              │
│  ──────────        Used as the [[Prototype]] for objects                 │
│                    created with new                                      │
│                                                                          │
│  ─────────────────────────────────────────────────────────────────────   │
│                                                                          │
│     function Player(name) { this.name = name }                           │
│                                                                          │
│     Player.prototype ─────────────┐                                      │
│                                   │                                      │
│     const p = new Player("A")     │                                      │
│           │                       │                                      │
│           │ [[Prototype]] ════════╧═══▶ { constructor: Player }          │
│           │                                      │                       │
│           ▼                                      │ [[Prototype]]         │
│     ┌───────────┐                                ▼                       │
│     │  p        │                       Object.prototype                 │
│     │───────────│                                │                       │
│     │name: "A"  │                                │ [[Prototype]]         │
│     └───────────┘                                ▼                       │
│                                                null                      │
│                                                                          │
└─────────────────────────────────────────────────────────────────────────┘
Don’t use __proto__ in production code! It’s deprecated and has performance issues. Use Object.getPrototypeOf() to read and Object.setPrototypeOf() to write (sparingly).

How Property Lookup Works

When you access a property on an object, JavaScript performs a prototype chain lookup:
1

Check the object itself

JavaScript first looks for the property directly on the object.
2

Check the prototype

If not found, it looks at Object.getPrototypeOf(obj) (the object’s prototype).
3

Continue up the chain

If still not found, it checks the prototype’s prototype, and so on.
4

Reach null or find the property

The search stops when the property is found OR when null is reached (property is undefined).
const grandparent = {
  familyName: "Smith",
  sayHello() {
    return `Hello from the ${this.familyName} family!`
  }
}

const parent = Object.create(grandparent)
parent.job = "Engineer"

const child = Object.create(parent)
child.name = "Alice"

// Property lookup in action:
console.log(child.name)        // "Alice" (found on child)
console.log(child.job)         // "Engineer" (found on parent)
console.log(child.familyName)  // "Smith" (found on grandparent)
console.log(child.sayHello())  // "Hello from the Smith family!"
console.log(child.age)         // undefined (not found anywhere)

// Visualizing the chain
console.log(Object.getPrototypeOf(child) === parent)       // true
console.log(Object.getPrototypeOf(parent) === grandparent) // true
console.log(Object.getPrototypeOf(grandparent) === Object.prototype) // true
console.log(Object.getPrototypeOf(Object.prototype))       // null

Property Shadowing

When you set a property on an object, it creates or updates the property on that object, even if a property with the same name exists on the prototype:
const prototype = {
  greeting: "Hello",
  count: 0
}

const obj = Object.create(prototype)

// Reading — uses prototype's value
console.log(obj.greeting)  // "Hello" (from prototype)
console.log(obj.count)     // 0 (from prototype)

// Writing — creates property on obj, "shadows" the prototype's
obj.greeting = "Hi"
obj.count = 5

console.log(obj.greeting)        // "Hi" (own property)
console.log(prototype.greeting)  // "Hello" (unchanged!)

console.log(obj.count)           // 5 (own property)
console.log(prototype.count)     // 0 (unchanged!)

// Check what's "own" vs inherited
console.log(obj.hasOwnProperty("greeting"))  // true (it's on obj now)
console.log(obj.hasOwnProperty("count"))     // true
┌─────────────────────────────────────────────────────────────────────────┐
│                       PROPERTY SHADOWING                                 │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                          │
│   BEFORE obj.greeting = "Hi"           AFTER obj.greeting = "Hi"         │
│   ──────────────────────────           ─────────────────────────         │
│                                                                          │
│   obj                                  obj                               │
│   ┌─────────────┐                      ┌──────────────────┐              │
│   │ (empty)     │                      │ greeting: "Hi"   │ ◄── shadows  │
│   │ [[Proto]]───┼──┐                   │ [[Proto]]────────┼──┐           │
│   └─────────────┘  │                   └──────────────────┘  │           │
│                    │                                         │           │
│                    ▼                                         ▼           │
│   prototype        prototype                                             │
│   ┌──────────────────────┐             ┌──────────────────────┐          │
│   │ greeting: "Hello"    │             │ greeting: "Hello"    │ hidden   │
│   │ count: 0             │             │ count: 0             │          │
│   └──────────────────────┘             └──────────────────────┘          │
│                                                                          │
│   obj.greeting returns "Hello"         obj.greeting returns "Hi"         │
│   (found on prototype)                 (found on obj, stops looking)     │
│                                                                          │
└─────────────────────────────────────────────────────────────────────────┘

Ways to Create Objects in JavaScript

JavaScript gives you several ways to create objects, each with different use cases:

1. Object Literals

The simplest way. Great for one-off objects:
// Object literal — prototype is automatically Object.prototype
const player = {
  name: "Alice",
  health: 100,
  attack() {
    return `${this.name} attacks!`
  }
}

console.log(Object.getPrototypeOf(player) === Object.prototype)  // true

2. Object.create() — Create with Specific Prototype

Object.create() creates a new object with a specified prototype:
// Create a prototype object
const animalProto = {
  speak() {
    return `${this.name} makes a sound.`
  },
  eat(food) {
    return `${this.name} eats ${food}.`
  }
}

// Create objects that inherit from animalProto
const dog = Object.create(animalProto)
dog.name = "Rex"
dog.breed = "German Shepherd"

const cat = Object.create(animalProto)
cat.name = "Whiskers"
cat.color = "orange"

console.log(dog.speak())  // "Rex makes a sound."
console.log(cat.eat("fish"))  // "Whiskers eats fish."

// Both share the same prototype
console.log(Object.getPrototypeOf(dog) === animalProto)  // true
console.log(Object.getPrototypeOf(cat) === animalProto)  // true

Creating Objects with No Prototype

Pass null to create an object with no prototype. This is useful for dictionaries:
// Regular object inherits from Object.prototype
const regular = {}
console.log(regular.toString)  // [Function: toString]
console.log("toString" in regular)  // true

// Object with null prototype — truly empty
const dict = Object.create(null)
console.log(dict.toString)  // undefined
console.log("toString" in dict)  // false

// Useful for safe dictionaries (no inherited properties to collide with)
dict["hasOwnProperty"] = "I can use any key!"
console.log(dict["hasOwnProperty"])  // "I can use any key!"

// With regular object, this would shadow the method:
const risky = {}
risky["hasOwnProperty"] = "oops"
// risky.hasOwnProperty("x") would now throw an error!

Object.create() with Property Descriptors

You can define properties with descriptors:
const person = Object.create(Object.prototype, {
  name: {
    value: "Alice",
    writable: true,
    enumerable: true,
    configurable: true
  },
  age: {
    value: 30,
    writable: false,  // Can't change age
    enumerable: true,
    configurable: false
  },
  secret: {
    value: "hidden",
    enumerable: false  // Won't show in for...in or Object.keys()
  }
})

console.log(person.name)  // "Alice"
console.log(person.age)   // 30
person.age = 25           // Silently fails (or throws in strict mode)
console.log(person.age)   // Still 30

console.log(Object.keys(person))  // ["name", "age"] (no "secret")

3. The new Operator — Create from Constructor

The new operator creates an object from a constructor function. When you call new Constructor(args), JavaScript performs 4 steps:
1

Create a new empty object

JavaScript creates a fresh object: const obj = {}
2

Link the prototype

Sets obj’s [[Prototype]] to Constructor.prototype (if it’s an object). If Constructor.prototype is not an object (e.g., a primitive), the new object uses Object.prototype instead.
3

Execute the constructor

Runs the constructor with this bound to the new object
4

Return the object

Returns obj (unless the constructor explicitly returns a non-primitive value)
// A constructor function
function Player(name, health) {
  // Step 3: 'this' is bound to the new object
  this.name = name
  this.health = health
}

// Methods go on the prototype (shared by all instances)
Player.prototype.attack = function() {
  return `${this.name} attacks!`
}

// Create instance with 'new'
const alice = new Player("Alice", 100)

console.log(alice.name)    // "Alice"
console.log(alice.attack())  // "Alice attacks!"
console.log(alice instanceof Player)  // true
console.log(Object.getPrototypeOf(alice) === Player.prototype)  // true
┌─────────────────────────────────────────────────────────────────────────┐
│               WHAT new Player("Alice", 100) DOES                         │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                          │
│  Step 1: Create a new empty object                                       │
│          const obj = {}                                                  │
│                                                                          │
│  Step 2: Link the object's prototype to Constructor.prototype            │
│          Object.setPrototypeOf(obj, Player.prototype)                    │
│                                                                          │
│  Step 3: Run the constructor with 'this' bound to the new object         │
│          Player.call(obj, "Alice", 100)                                  │
│          // Now obj.name = "Alice", obj.health = 100                     │
│                                                                          │
│  Step 4: Return the object (unless constructor returns an object)        │
│          return obj                                                      │
│                                                                          │
│  ─────────────────────────────────────────────────────────────────────   │
│                                                                          │
│  RESULT:                                                                 │
│                                                                          │
│     Player.prototype                                                     │
│     ┌─────────────────────┐                                              │
│     │ attack: function()  │◄───── Shared by all instances                │
│     │ constructor: Player │                                              │
│     └─────────────────────┘                                              │
│              ▲                                                           │
│              │ [[Prototype]]                                             │
│              │                                                           │
│     ┌────────┴────────┐                                                  │
│     │      alice      │                                                  │
│     │─────────────────│                                                  │
│     │ name: "Alice"   │                                                  │
│     │ health: 100     │                                                  │
│     └─────────────────┘                                                  │
│                                                                          │
└─────────────────────────────────────────────────────────────────────────┘

Simulating new

Here’s a function that does what new does:
function myNew(Constructor, ...args) {
  // Steps 1 & 2: Create object with correct prototype
  const obj = Object.create(Constructor.prototype)
  
  // Step 3: Run constructor with 'this' = obj
  const result = Constructor.apply(obj, args)
  
  // Step 4: Return result if it's a non-primitive, otherwise return obj
  // Note: Functions are also objects, so constructors returning functions
  // will override the default return as well
  return (result !== null && typeof result === 'object') ? result : obj
}

// These do the same thing:
const player1 = new Player("Alice", 100)
const player2 = myNew(Player, "Bob", 100)

console.log(player1 instanceof Player)  // true
console.log(player2 instanceof Player)  // true
Edge case: If a constructor returns a function, that function is returned instead of the new object (since functions are objects in JavaScript). This is rare in practice but technically allowed by the spec.
Don’t forget new! Without it, this in a constructor refers to the global object (or undefined in strict mode), causing bugs. ES6 classes throw an error if you forget new, which is safer.

4. Object.assign() — Copy Properties

Object.assign() copies enumerable own properties from source objects to a target:
// Basic usage: copy properties to target
const target = { a: 1 }
const source = { b: 2, c: 3 }

const result = Object.assign(target, source)

console.log(result)  // { a: 1, b: 2, c: 3 }
console.log(target)  // { a: 1, b: 2, c: 3 } — target is modified!
console.log(result === target)  // true — returns the target

Merging Multiple Objects

const defaults = { theme: "light", fontSize: 14, showSidebar: true }
const userPrefs = { theme: "dark", fontSize: 16 }
const sessionOverrides = { fontSize: 18 }

// Later sources overwrite earlier ones
const settings = Object.assign({}, defaults, userPrefs, sessionOverrides)

console.log(settings)
// { theme: "dark", fontSize: 18, showSidebar: true }

// Original objects are unchanged (because we used {} as target)
console.log(defaults.fontSize)  // 14

Cloning Objects (Shallow)

const original = { name: "Alice", scores: [90, 85, 92] }

// Shallow clone
const clone = Object.assign({}, original)

clone.name = "Bob"
console.log(original.name)  // "Alice" — primitive copied by value

clone.scores.push(100)
console.log(original.scores)  // [90, 85, 92, 100] — array shared!
Object.assign() performs a shallow copy! Nested objects and arrays are copied by reference, not cloned. For deep cloning, use structuredClone() or a library like Lodash.
// Deep clone with structuredClone (modern browsers)
const deepClone = structuredClone(original)
deepClone.scores.push(100)
console.log(original.scores)  // [90, 85, 92] — unchanged!

Object.assign() Only Copies Own, Enumerable Properties

const proto = { inherited: "from prototype" }
const source = Object.create(proto)
source.own = "my own property"

Object.defineProperty(source, "hidden", {
  value: "non-enumerable",
  enumerable: false
})

const target = {}
Object.assign(target, source)

console.log(target.own)        // "my own property" — copied
console.log(target.inherited)  // undefined — NOT copied (inherited)
console.log(target.hidden)     // undefined — NOT copied (non-enumerable)

Inspecting and Modifying Prototypes

JavaScript provides methods to work with prototypes:

Object.getPrototypeOf() — Read the Prototype

const player = { name: "Alice" }

// Get the prototype
const proto = Object.getPrototypeOf(player)
console.log(proto === Object.prototype)  // true

// Works with any object
function Game() {}
const game = new Game()
console.log(Object.getPrototypeOf(game) === Game.prototype)  // true

// End of the chain
console.log(Object.getPrototypeOf(Object.prototype))  // null

Object.setPrototypeOf() — Change the Prototype

Object.setPrototypeOf() changes an object’s prototype after creation:
const swimmer = {
  swim() { return `${this.name} swims!` }
}

const flyer = {
  fly() { return `${this.name} flies!` }
}

const duck = { name: "Donald" }

// Start as a swimmer
Object.setPrototypeOf(duck, swimmer)
console.log(duck.swim())  // "Donald swims!"

// Change to a flyer
Object.setPrototypeOf(duck, flyer)
console.log(duck.fly())   // "Donald flies!"
// console.log(duck.swim())  // TypeError: duck.swim is not a function
Avoid Object.setPrototypeOf() in performance-critical code! Changing an object’s prototype after creation is slow and can deoptimize your code. Set the prototype correctly at creation time with Object.create() instead.

instanceof — Check the Prototype Chain

The instanceof operator checks if Constructor.prototype exists in the object’s prototype chain:
function Animal(name) {
  this.name = name
}

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

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

const rex = new Dog("Rex", "German Shepherd")

console.log(rex instanceof Dog)     // true
console.log(rex instanceof Animal)  // true
console.log(rex instanceof Object)  // true
console.log(rex instanceof Array)   // false

isPrototypeOf() — Check if Object is in Chain

const animal = { eats: true }
const dog = Object.create(animal)
dog.barks = true

console.log(animal.isPrototypeOf(dog))  // true
console.log(Object.prototype.isPrototypeOf(dog))  // true
console.log(Array.prototype.isPrototypeOf(dog))   // false

Common Prototype Methods

These methods help you work with object properties and prototypes:

hasOwnProperty() — Check Own Properties

const proto = { inherited: true }
const obj = Object.create(proto)
obj.own = true

// hasOwnProperty checks ONLY the object, not the chain
console.log(obj.hasOwnProperty("own"))        // true
console.log(obj.hasOwnProperty("inherited"))  // false

// 'in' operator checks the whole chain
console.log("own" in obj)        // true
console.log("inherited" in obj)  // true
Modern alternative: Object.hasOwn() (ES2022+)Use Object.hasOwn() instead of hasOwnProperty(). It’s safer because it works on objects with a null prototype and can’t be shadowed:
// hasOwnProperty can be shadowed or unavailable
const nullProto = Object.create(null)
nullProto.key = "value"
// nullProto.hasOwnProperty("key")  // TypeError: not a function

// Object.hasOwn always works
Object.hasOwn(nullProto, "key")  // true

Object.keys() vs for…in

const proto = { inherited: "value" }
const obj = Object.create(proto)
obj.own1 = "a"
obj.own2 = "b"

// Object.keys() — only own enumerable properties
console.log(Object.keys(obj))  // ["own1", "own2"]

// for...in — own AND inherited enumerable properties
for (const key in obj) {
  console.log(key)  // "own1", "own2", "inherited"
}

// Filter for...in to only own properties
for (const key in obj) {
  if (obj.hasOwnProperty(key)) {
    console.log(key)  // "own1", "own2"
  }
}

Object.getOwnPropertyNames() — All Own Properties

const obj = { visible: true }
Object.defineProperty(obj, "hidden", {
  value: "secret",
  enumerable: false
})

// Object.keys() — only enumerable
console.log(Object.keys(obj))  // ["visible"]

// Object.getOwnPropertyNames() — all own properties
console.log(Object.getOwnPropertyNames(obj))  // ["visible", "hidden"]

Summary Table

MethodOwn?Enumerable?Inherited?
obj.hasOwnProperty(key)YesBothNo
key in objYesBothYes
Object.keys(obj)YesYes onlyNo
Object.getOwnPropertyNames(obj)YesBothNo
for...inYesYes onlyYes

The Prototype Pitfall: Common Mistakes

Mistake 1: Modifying Object.prototype

// ❌ NEVER do this!
Object.prototype.greet = function() {
  return "Hello!"
}

// Now EVERY object has greet()
const player = { name: "Alice" }
const numbers = [1, 2, 3]
const date = new Date()

console.log(player.greet())   // "Hello!"
console.log(numbers.greet())  // "Hello!"
console.log(date.greet())     // "Hello!"

// This can break for...in loops
for (const key in player) {
  console.log(key)  // "name", "greet" — greet shows up!
}

// And cause conflicts with libraries
Never modify Object.prototype! It affects every object in your application and can break third-party code. If you need to add methods to all objects of a type, create your own constructor or class.

Mistake 2: Confusing .prototype with [[Prototype]]

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

const alice = new Player("Alice")

// ❌ WRONG — instances don't have .prototype
console.log(alice.prototype)  // undefined

// ✓ CORRECT — use Object.getPrototypeOf()
console.log(Object.getPrototypeOf(alice) === Player.prototype)  // true

// .prototype is ONLY on functions
console.log(Player.prototype)  // { constructor: Player }

Mistake 3: Prototype Pollution

Prototype pollution occurs when attackers can modify Object.prototype, affecting all objects. This is a real security vulnerability:
// ❌ DANGEROUS - merging untrusted data can pollute prototypes
const maliciousPayload = JSON.parse('{"__proto__": {"isAdmin": true}}')

const user = {}
Object.assign(user, maliciousPayload)  // Pollution via Object.assign!

// Now ALL objects have isAdmin!
const anotherUser = {}
console.log(anotherUser.isAdmin)  // true - polluted!

// ✓ SAFER - use null prototype objects for dictionaries
const safeDict = Object.create(null)
safeDict["__proto__"] = "safe"  // Just a regular property, no pollution

// ✓ SAFEST - use Map for key-value storage with untrusted keys
const map = new Map()
map.set("__proto__", "value")  // Completely safe
Prototype pollution attacks can occur through Object.assign(), object spread ({...obj}), deep merge utilities, and JSON parsing. Always sanitize untrusted input and consider using Object.create(null) or Map for user-controlled keys.

Mistake 4: Shared Reference on Prototype

// ❌ WRONG — array on prototype is shared by all instances
function Player(name) {
  this.name = name
}
Player.prototype.inventory = []  // Shared by ALL players!

const alice = new Player("Alice")
const bob = new Player("Bob")

alice.inventory.push("sword")
console.log(bob.inventory)  // ["sword"] — Bob has Alice's sword!

// ✓ CORRECT — initialize arrays in constructor
function Player(name) {
  this.name = name
  this.inventory = []  // Each player gets their own array
}

Key Takeaways

Key things to remember about prototypes and object creation:
  1. Every object has a prototype — a hidden link ([[Prototype]]) to another object, forming a chain that ends at null
  2. Property lookup walks the chain — JavaScript searches the object first, then its prototype, then the prototype’s prototype, and so on
  3. [[Prototype]] vs .prototype[[Prototype]] is the internal link every object has; .prototype is a property on functions used with new
  4. Use Object.getPrototypeOf() — not __proto__, which is deprecated
  5. Object.create(proto) — creates an object with a specific prototype; pass null for no prototype
  6. The new operator does 4 things — creates object, links prototype, runs constructor with this, returns the object
  7. Object.assign() is shallow — nested objects are copied by reference, not cloned
  8. hasOwnProperty() vs inhasOwnProperty checks only the object; in checks the whole prototype chain
  9. Never modify Object.prototype — it affects all objects and can break code
  10. Put methods on the prototype — for memory efficiency, don’t define methods in the constructor

Test Your Knowledge

Answer:The prototype chain is JavaScript’s inheritance mechanism. Every object has a [[Prototype]] link to another object (its prototype).When you access a property:
  1. JavaScript looks for it on the object itself
  2. If not found, looks on the object’s prototype
  3. Continues up the chain until found or null is reached
const parent = { greet: "Hello" }
const child = Object.create(parent)

console.log(child.greet)  // "Hello" — found on prototype
console.log(child.missing)  // undefined — not found anywhere
Answer:
  • [[Prototype]]: The internal slot every object has, pointing to its prototype. Not directly accessible.
  • __proto__: A deprecated getter/setter that exposes [[Prototype]]. Use Object.getPrototypeOf() instead.
  • .prototype: A property that exists only on functions. When you use new, the created object’s [[Prototype]] is set to this value.
function Foo() {}
const f = new Foo()

// f's [[Prototype]] is Foo.prototype
Object.getPrototypeOf(f) === Foo.prototype  // true

// Foo is a function, so it has .prototype
Foo.prototype  // { constructor: Foo }

// f is NOT a function, so it has no .prototype
f.prototype  // undefined
Answer:When you call new Constructor(args):
  1. Create a new empty object {}
  2. Link the object’s [[Prototype]] to Constructor.prototype
  3. Execute the constructor with this bound to the new object
  4. Return the object (unless the constructor returns a different object)
function myNew(Constructor, ...args) {
  const obj = Object.create(Constructor.prototype)  // Steps 1-2
  const result = Constructor.apply(obj, args)       // Step 3
  return (typeof result === 'object' && result !== null) ? result : obj  // Step 4
}
Answer:
  • Object.create(proto) creates an object with the specified object as its prototype. It doesn’t call any constructor.
  • new Constructor() creates an object with Constructor.prototype as its prototype AND runs the constructor function.
const proto = { greet() { return "Hi!" } }

// Object.create — just links the prototype
const obj1 = Object.create(proto)

// new — links prototype AND runs constructor
function MyClass() {
  this.initialized = true
}
MyClass.prototype = proto

const obj2 = new MyClass()
console.log(obj2.initialized)  // true (constructor ran)
console.log(obj1.initialized)  // undefined (no constructor)
Answer:Modifying Object.prototype affects every object in your application because all objects inherit from it. This can:
  1. Break for...in loops (new properties show up)
  2. Conflict with third-party libraries
  3. Cause unexpected behavior throughout your codebase
// ❌ BAD
Object.prototype.bad = "affects everything"

const obj = {}
for (const key in obj) {
  console.log(key)  // "bad" — unexpected!
}
Instead, create your own constructors/classes or use composition.
Answer:Shallow copy: Copies the top-level properties. Nested objects/arrays are copied by reference (they point to the same data).Deep copy: Recursively copies all levels. Nested objects/arrays are fully cloned.
const original = { 
  name: "Alice",
  scores: [90, 85]  // nested array
}

// Shallow copy with Object.assign
const shallow = Object.assign({}, original)
shallow.scores.push(100)
console.log(original.scores)  // [90, 85, 100] — modified!

// Deep copy with structuredClone
const deep = structuredClone(original)
deep.scores.push(100)
console.log(original.scores)  // [90, 85, 100] — still modified from before
// But if we had deep copied first, original would be unchanged

Frequently Asked Questions

The prototype chain is JavaScript’s inheritance mechanism. Every object has an internal [[Prototype]] link to another object. When you access a property, JavaScript looks on the object first, then follows the chain of prototypes until it finds the property or reaches null. As described in the ECMAScript specification, this delegation model is what powers all object inheritance in JavaScript.
__proto__ is an accessor property on every object that points to its prototype (the object it inherits from). .prototype is a property on constructor functions that becomes the __proto__ of objects created with new. According to MDN, __proto__ is a legacy feature — use Object.getPrototypeOf() and Object.setPrototypeOf() instead.
Object.create(proto) creates a new object with its [[Prototype]] set to the specified object. Unlike new, it does not call a constructor function. This gives you direct control over the prototype chain. It is the cleanest way to set up prototypal inheritance without the complexity of constructor functions.
The new operator performs four steps: creates an empty object, sets the object’s [[Prototype]] to the constructor’s .prototype, calls the constructor with this bound to the new object, and returns the object. If the constructor explicitly returns an object, that object is returned instead. This is how both constructor functions and classes create instances.
In prototypal inheritance, objects inherit directly from other objects through the prototype chain. In classical inheritance (Java, C++), classes define blueprints and instances are created from those blueprints. JavaScript uses prototypal delegation, meaning an object delegates property lookups to its prototype. The class syntax in ES6 is syntactic sugar over this prototype-based model.


Reference

Articles

Videos

Last modified on February 17, 2026