Property lookup follows JavaScript prototype chains until a property is found or the chain ends. Constructors and classes organize that model, while receiver, own-property, descriptor, and mutation rules remain fundamental.
Avoid global built-in patches, block prototype-pollution paths, inspect descriptors safely, and use composition when inheritance would couple unrelated behavior.
JavaScript is a prototype-based language. This means objects can inherit properties and methods from other objects. Instead of copying every method into every object, JavaScript lets objects share behavior through a prototype chain.
Modern JavaScript has class syntax, but classes still use prototypes internally. Understanding prototypes helps you understand objects, inheritance, methods, this, new, class, and many interview questions much more clearly.
When you access a property on an object, JavaScript first checks the object itself. If the property is not found, JavaScript looks at the object's prototype. If it is not found there, JavaScript continues up the prototype chain until it finds the property or reaches null.
The object user does not define toString, but it can still access it because ordinary objects inherit from Object.prototype.
const user = {
name: 'Asha'
};
console.log(user.name); // Asha - found on user
console.log(user.toString); // found on Object.prototype
// JavaScript looks like this:
// user -> Object.prototype -> null
This topic has confusing names, so separate these two ideas:
function Student(name) {
this.name = name;
}
const student = new Student('Riya');
console.log(Student.prototype); // object used for Student instances
console.log(Object.getPrototypeOf(student) === Student.prototype); // true
Before ES6 classes, constructor functions were the common way to create many similar objects. A constructor function is called with new. Methods should usually be placed on the constructor's prototype so all instances can share one method instead of creating a new function for every instance.
The two objects have different data, but they share the same introduce() method through Student.prototype.
function Student(name, course) {
this.name = name;
this.course = course;
}
Student.prototype.introduce = function() {
return `${this.name} is learning ${this.course}.`;
};
const student1 = new Student('Aman', 'JavaScript');
const student2 = new Student('Neha', 'React');
console.log(student1.introduce()); // Aman is learning JavaScript.
console.log(student2.introduce()); // Neha is learning React.
console.log(student1.introduce === student2.introduce); // true
Properties placed directly on an instance belong only to that object. Properties placed on the prototype are shared through inheritance.
function Student(name) {
this.name = name; // own property
}
Student.prototype.school = 'Tutorials Logic'; // shared prototype property
const student1 = new Student('Rohit');
const student2 = new Student('Kavya');
student1.age = 22; // only student1 has age
console.log(student1.name); // Rohit
console.log(student1.school); // Tutorials Logic
console.log(student2.school); // Tutorials Logic
console.log(student2.age); // undefined
A prototype can also have its own prototype. This creates a chain. JavaScript walks this chain during property lookup.
Here, dog inherits from Dog.prototype, and Dog.prototype inherits from Animal.prototype. If JavaScript cannot find speak() on the dog itself or on Dog.prototype, it finds it on Animal.prototype.
function Animal(name) {
this.name = name;
}
Animal.prototype.speak = function() {
return `${this.name} makes a sound.`;
};
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() {
return `${this.name} barks.`;
};
const dog = new Dog('Rex', 'Labrador');
console.log(dog.bark()); // Rex barks.
console.log(dog.speak()); // Rex makes a sound.
console.log(dog instanceof Dog); // true
console.log(dog instanceof Animal); // true
Use Object.getPrototypeOf() to inspect an object's prototype. Use Object.setPrototypeOf() rarely, because changing prototypes after objects are created can hurt performance and make code harder to reason about.
in checks the object and its prototype chain. Object.hasOwn() checks only the object's own properties.
const user = { name: 'Meera' };
console.log(Object.getPrototypeOf(user) === Object.prototype); // true
console.log(Object.getPrototypeOf(Object.prototype)); // null
console.log('toString' in user); // true, inherited
console.log(Object.hasOwn(user, 'toString')); // false
console.log(Object.hasOwn(user, 'name')); // true
Object.create() creates a new object with a specific prototype. It is a direct way to build prototype inheritance without using a constructor function.
const userMethods = {
greet() {
return `Hello, ${this.name}`;
}
};
const user = Object.create(userMethods);
user.name = 'Anaya';
console.log(user.greet()); // Hello, Anaya
console.log(Object.getPrototypeOf(user) === userMethods); // true
Sometimes you may want a plain dictionary object with no inherited properties. Object.create(null) creates an object whose prototype is null. This can be useful for maps or lookup tables, though Map is often better in modern JavaScript.
const dictionary = Object.create(null);
dictionary.javascript = 'A programming language';
dictionary.prototype = 'An inheritance object';
console.log(dictionary.javascript);
console.log(dictionary.toString); // undefined
console.log(Object.getPrototypeOf(dictionary)); // null
The class syntax is the modern way to write constructor-style object code. It is easier to read, but instance methods are still stored on the class prototype.
The title property belongs to the instance. The getTitle() method belongs to Course.prototype and is shared by all Course instances.
class Course {
constructor(title) {
this.title = title;
}
getTitle() {
return this.title;
}
}
const jsCourse = new Course('JavaScript');
console.log(jsCourse.getTitle()); // JavaScript
console.log(Object.getPrototypeOf(jsCourse) === Course.prototype); // true
console.log(Course.prototype.getTitle === jsCourse.getTitle); // true
When a class extends another class, JavaScript sets up a prototype chain between the child prototype and the parent prototype.
class Vehicle {
constructor(make) {
this.make = make;
}
describe() {
return `Vehicle: ${this.make}`;
}
}
class Car extends Vehicle {
constructor(make, model) {
super(make);
this.model = model;
}
details() {
return `${this.describe()} ${this.model}`;
}
}
const car = new Car('Toyota', 'Camry');
console.log(car.details()); // Vehicle: Toyota Camry
console.log(Object.getPrototypeOf(Car.prototype) === Vehicle.prototype); // true
Classes also have a prototype relationship at the constructor level. This is why static methods can be inherited by child classes.
Instance methods are found through Formatter.prototype. Static methods are found through the constructor function itself.
class Tool {
static category() {
return 'Developer Tool';
}
}
class Formatter extends Tool {}
console.log(Formatter.category()); // Developer Tool
console.log(Object.getPrototypeOf(Formatter) === Tool); // true
If an object has its own property with the same name as a prototype property, the own property wins. This is called shadowing.
const defaults = {
theme: 'light'
};
const settings = Object.create(defaults);
console.log(settings.theme); // light - inherited
settings.theme = 'dark';
console.log(settings.theme); // dark - own property shadows prototype
console.log(Object.hasOwn(settings, 'theme')); // true
Prototype properties are shared. That is good for methods, but dangerous for mutable data such as arrays and objects. Put instance-specific data inside the constructor instead.
function TodoList() {}
// Bad: every instance shares the same array.
TodoList.prototype.items = [];
const personal = new TodoList();
const work = new TodoList();
personal.items.push('Buy milk');
console.log(work.items); // ['Buy milk'] - unexpected
// Better:
function SafeTodoList() {
this.items = [];
}
Arrays, strings, dates, functions, and regular expressions all inherit methods from their built-in prototypes. For example, arrays get methods from Array.prototype.
const numbers = [1, 2, 3];
console.log(numbers.map(n => n * 2)); // [2, 4, 6]
console.log(Object.getPrototypeOf(numbers) === Array.prototype); // true
console.log(Object.getPrototypeOf(Array.prototype) === Object.prototype); // true
JavaScript allows you to add methods to built-in prototypes, but this is risky in production code. It can conflict with future JavaScript features, third-party libraries, or other team code.
// Possible, but usually not recommended.
Array.prototype.sum = function() {
return this.reduce((total, number) => total + number, 0);
};
console.log([1, 2, 3].sum()); // 6
// Safer alternative:
function sum(numbers) {
return numbers.reduce((total, number) => total + number, 0);
}
Prototype pollution is a security problem where unsafe code lets user-controlled input modify shared prototypes such as Object.prototype. This can affect many objects in the application. Be careful when merging untrusted objects, especially if keys like __proto__, constructor, or prototype are allowed.
In real applications, use well-maintained libraries and keep dependencies updated. Avoid writing deep merge logic for untrusted data unless you fully understand the security risks.
function safeAssign(target, source) {
for (const key of Object.keys(source)) {
if (key === '__proto__' || key === 'constructor' || key === 'prototype') {
continue;
}
target[key] = source[key];
}
return target;
}
const settings = safeAssign({}, JSON.parse('{"theme":"dark"}'));
console.log(settings.theme); // dark
Prototype methods usually need dynamic this, so regular functions are normally the right choice. Arrow functions do not have their own this, which can cause confusing bugs when used as prototype methods.
function User(name) {
this.name = name;
}
// Good: regular function gets this from the call site.
User.prototype.greet = function() {
return `Hello, ${this.name}`;
};
const user = new User('Dev');
console.log(user.greet()); // Hello, Dev
The example below creates reusable course objects. Instance properties store each course's data, while prototype methods define shared behavior.
function Course(title, lessons) {
this.title = title;
this.lessons = lessons;
this.completed = 0;
}
Course.prototype.completeLesson = function() {
if (this.completed < this.lessons) {
this.completed += 1;
}
};
Course.prototype.getProgress = function() {
return `${this.completed}/${this.lessons} lessons completed`;
};
const js = new Course('JavaScript Prototypes', 12);
js.completeLesson();
js.completeLesson();
console.log(js.getProgress()); // 2/12 lessons completed
If someone asks "What is a prototype in JavaScript?", answer with the object lookup idea first. Then mention constructor functions and classes.
A strong answer: "A prototype is an object that another object can inherit from. When JavaScript cannot find a property directly on an object, it searches that object's prototype chain. Constructor functions and classes use prototypes to share methods across instances."
function Person(name) {
this.name = name;
}
Person.prototype.sayHi = function() {
return `Hi, I am ${this.name}`;
};
const person = new Person('Asha');
console.log(person.sayHi()); // Hi, I am Asha
console.log(Object.getPrototypeOf(person) === Person.prototype); // true
| Term | Meaning |
|---|---|
| [[Prototype]] | The internal link from one object to another object it inherits from. |
| Function.prototype | The object assigned as the prototype of instances created with new. |
| Object.getPrototypeOf(obj) | Returns the prototype of an object. |
| Object.create(proto) | Creates a new object with proto as its prototype. |
| instanceof | Checks whether a constructor's prototype appears in an object's prototype chain. |
| Object.hasOwn(obj, key) | Checks whether a property belongs directly to the object. |
Prototypes are the foundation of JavaScript inheritance. Every normal object can inherit from another object through its prototype. Constructor functions and classes use prototypes to share methods efficiently across instances. Once you understand property lookup, prototype chains, Object.create(), and how classes map to prototypes, JavaScript's object model becomes much easier to reason about.
Avoid modifying built-in prototypes in application code because changes affect unrelated consumers and can collide with future platform methods. Prefer local helpers, subclasses where substitution is valid, or composition. Cross-realm values may have different intrinsic prototypes, so prototype identity checks are not universal type validation.
Prototype pollution occurs when untrusted keys alter a shared prototype through unsafe merge or path-setting behavior. Reject dangerous keys, copy only schema-approved fields, prefer `Object.hasOwn` for ownership checks, and use null-prototype dictionaries where inherited properties are unwanted.
Debug lookup with own property descriptors, `Object.getPrototypeOf`, and a bounded walk of the chain. Getters and proxies can execute during ordinary inspection, so use descriptors when side effects matter. Keep object shapes predictable and measure before changing prototype strategy for performance.
A prototype is an object that another object inherits from. If a property is not found directly on an object, JavaScript searches the prototype chain.
Classes are modern syntax for creating constructor-style objects, but they still use prototypes internally. Instance methods declared in a class are placed on the class prototype.
<code>prototype</code> is a property on functions used when creating instances with <code>new</code>. <code>__proto__</code> is an older accessor for an object's internal prototype. Prefer <code>Object.getPrototypeOf()</code> instead of <code>__proto__</code>.
Explore 500+ free tutorials across 20+ languages and frameworks.