Learn JavaScript prototypes and object creation. Understand the prototype chain, new operator, Object.create(), and inheritance.
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?
Copy
Ask AI
// You create a simple objectconst 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!
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:
First looks for the property on the object itself
If not found, looks on the object’s prototype
If still not found, looks on the prototype’s prototype
Continues until it finds the property or reaches null (the end of the chain)
Copy
Ask AI
// Create a simple objectconst wizard = { name: "Gandalf", castSpell() { return `${this.name} casts a spell!` }}// Create another object that inherits from wizardconst apprentice = Object.create(wizard)apprentice.name = "Harry"// apprentice has its own 'name' propertyconsole.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 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:
Term
What It Is
How to Access
[[Prototype]]
The internal prototype link every object has
Not directly accessible (it’s internal)
__proto__
A getter/setter that exposes [[Prototype]]
obj.__proto__ (deprecated, avoid in production)
.prototype
A property on functions used when creating instances with new
Function.prototype
Copy
Ask AI
// Every object has [[Prototype]] — an internal link to its prototypeconst player = { name: "Alice" }// __proto__ exposes [[Prototype]] (deprecated but works)console.log(player.__proto__ === Object.prototype) // true// .prototype exists only on FUNCTIONSfunction Player(name) { this.name = name}// When you use 'new Player()', the new object's [[Prototype]]// is set to Player.prototypeconsole.log(Player.prototype) // { constructor: Player }const alice = new Player("Alice")console.log(Object.getPrototypeOf(alice) === Player.prototype) // true
Copy
Ask AI
┌─────────────────────────────────────────────────────────────────────────┐│ 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).
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:
Pass null to create an object with no prototype. This is useful for dictionaries:
Copy
Ask AI
// Regular object inherits from Object.prototypeconst regular = {}console.log(regular.toString) // [Function: toString]console.log("toString" in regular) // true// Object with null prototype — truly emptyconst dict = Object.create(null)console.log(dict.toString) // undefinedconsole.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!
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)
Copy
Ask AI
// A constructor functionfunction 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) // trueconsole.log(Object.getPrototypeOf(alice) === Player.prototype) // true
Copy
Ask AI
┌─────────────────────────────────────────────────────────────────────────┐│ 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 │ ││ └─────────────────┘ ││ │└─────────────────────────────────────────────────────────────────────────┘
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) // trueconsole.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.
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.
Copy
Ask AI
// Deep clone with structuredClone (modern browsers)const deepClone = structuredClone(original)deepClone.scores.push(100)console.log(original.scores) // [90, 85, 92] — unchanged!
const player = { name: "Alice" }// Get the prototypeconst proto = Object.getPrototypeOf(player)console.log(proto === Object.prototype) // true// Works with any objectfunction Game() {}const game = new Game()console.log(Object.getPrototypeOf(game) === Game.prototype) // true// End of the chainconsole.log(Object.getPrototypeOf(Object.prototype)) // null
const swimmer = { swim() { return `${this.name} swims!` }}const flyer = { fly() { return `${this.name} flies!` }}const duck = { name: "Donald" }// Start as a swimmerObject.setPrototypeOf(duck, swimmer)console.log(duck.swim()) // "Donald swims!"// Change to a flyerObject.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.
const proto = { inherited: true }const obj = Object.create(proto)obj.own = true// hasOwnProperty checks ONLY the object, not the chainconsole.log(obj.hasOwnProperty("own")) // trueconsole.log(obj.hasOwnProperty("inherited")) // false// 'in' operator checks the whole chainconsole.log("own" in obj) // trueconsole.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:
Copy
Ask AI
// hasOwnProperty can be shadowed or unavailableconst nullProto = Object.create(null)nullProto.key = "value"// nullProto.hasOwnProperty("key") // TypeError: not a function// Object.hasOwn always worksObject.hasOwn(nullProto, "key") // true
// ❌ 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 loopsfor (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]]
Copy
Ask AI
function Player(name) { this.name = name}const alice = new Player("Alice")// ❌ WRONG — instances don't have .prototypeconsole.log(alice.prototype) // undefined// ✓ CORRECT — use Object.getPrototypeOf()console.log(Object.getPrototypeOf(alice) === Player.prototype) // true// .prototype is ONLY on functionsconsole.log(Player.prototype) // { constructor: Player }
Prototype pollution occurs when attackers can modify Object.prototype, affecting all objects. This is a real security vulnerability:
Copy
Ask AI
// ❌ DANGEROUS - merging untrusted data can pollute prototypesconst 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 dictionariesconst safeDict = Object.create(null)safeDict["__proto__"] = "safe" // Just a regular property, no pollution// ✓ SAFEST - use Map for key-value storage with untrusted keysconst 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.
// ❌ WRONG — array on prototype is shared by all instancesfunction 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 constructorfunction Player(name) { this.name = name this.inventory = [] // Each player gets their own array}
Question 1: What is the prototype chain and how does property lookup work?
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:
JavaScript looks for it on the object itself
If not found, looks on the object’s prototype
Continues up the chain until found or null is reached
Copy
Ask AI
const parent = { greet: "Hello" }const child = Object.create(parent)console.log(child.greet) // "Hello" — found on prototypeconsole.log(child.missing) // undefined — not found anywhere
Question 2: What's the difference between [[Prototype]], __proto__, and .prototype?
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.
Copy
Ask AI
function Foo() {}const f = new Foo()// f's [[Prototype]] is Foo.prototypeObject.getPrototypeOf(f) === Foo.prototype // true// Foo is a function, so it has .prototypeFoo.prototype // { constructor: Foo }// f is NOT a function, so it has no .prototypef.prototype // undefined
Question 3: What are the 4 steps the new keyword performs?
Answer:When you call new Constructor(args):
Create a new empty object {}
Link the object’s [[Prototype]] to Constructor.prototype
Execute the constructor with this bound to the new object
Return the object (unless the constructor returns a different object)
Copy
Ask AI
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}
Question 4: How does Object.create() differ from using new?
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.
Copy
Ask AI
const proto = { greet() { return "Hi!" } }// Object.create — just links the prototypeconst obj1 = Object.create(proto)// new — links prototype AND runs constructorfunction MyClass() { this.initialized = true}MyClass.prototype = protoconst obj2 = new MyClass()console.log(obj2.initialized) // true (constructor ran)console.log(obj1.initialized) // undefined (no constructor)
Question 5: Why should you avoid modifying Object.prototype?
Answer:Modifying Object.prototype affects every object in your application because all objects inherit from it. This can:
Break for...in loops (new properties show up)
Conflict with third-party libraries
Cause unexpected behavior throughout your codebase
Instead, create your own constructors/classes or use composition.
Question 6: What's the difference between Object.assign() shallow copy and deep copy?
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.
Copy
Ask AI
const original = { name: "Alice", scores: [90, 85] // nested array}// Shallow copy with Object.assignconst shallow = Object.assign({}, original)shallow.scores.push(100)console.log(original.scores) // [90, 85, 100] — modified!// Deep copy with structuredCloneconst 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
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.
What is the difference between __proto__ and prototype?
__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.
How does Object.create() work?
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.
What does the new operator do in JavaScript?
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.
What is prototypal inheritance and how is it different from classical inheritance?
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.