Quick Answer

Every JavaScript object holds a hidden link to another object, its prototype. When a property is not found on the object itself, the engine follows that link upwards until it finds one or reaches null. Functions carry a separate prototype property, which is the object handed to instances created with new. The class keyword builds exactly this structure for you, with stricter rules. It does not add a second inheritance system.

Prototype and __proto__ are not the same thing

Nearly every JavaScript beginner types user.prototype, gets back undefined, and loses an hour to it. The rule is short and worth memorising: only functions have a prototype property. Objects created from those functions do not.

Every object in JavaScript carries a hidden internal link to another object. The specification calls it [[Prototype]]. You read it properly with Object.getPrototypeOf(), and older code exposes it through the legacy accessor __proto__. That hidden link is the thing that makes inheritance work.

The prototype property on a function is something else entirely. It is a plain object sitting on the function, waiting to be handed out. When you call that function with new, the freshly created object's [[Prototype]] is pointed at it. So prototype is the template a constructor gives away, and __proto__ is the link an instance already holds. Same object, two names, depending on which side of new you are standing on.

function User(name) {
  this.name = name;
}
User.prototype.greet = function () {
  return 'Hi, ' + this.name;
};

const u = new User('Aarav');

u.greet();                                    // "Hi, Aarav"
u.prototype;                                  // undefined
User.prototype;                               // { greet: f }
Object.getPrototypeOf(u) === User.prototype;  // true

This also explains what new actually does, which is a standard interview question. It creates an empty object, sets that object's [[Prototype]] to User.prototype, calls User with this bound to the new object, and returns that object unless the constructor explicitly returns a different object. Four steps, no magic. Forget the new and in non-strict code this becomes the global object, so this.name = name quietly writes a global variable instead of building an instance.

How JavaScript actually finds a method

When you write u.greet(), the engine first looks for an own property called greet directly on u. There is none, so it follows the [[Prototype]] link to User.prototype and looks again. It finds the function there and calls it, with this still bound to u. If it had not been found, the search would continue to Object.prototype, then to null, and the expression would evaluate to undefined, followed by the familiar u.greet is not a function.

That upward walk is the prototype chain. Nothing is copied into your object. The methods stay in one place and every instance borrows them, which is why adding a method to a prototype instantly makes it available on objects that already exist.

const marks = [88, 92, 75];

Object.getPrototypeOf(marks) === Array.prototype;            // true
Object.getPrototypeOf(Array.prototype) === Object.prototype; // true
Object.getPrototypeOf(Object.prototype);                     // null

'map' in marks;               // true  - inherited from Array.prototype
marks.hasOwnProperty('map');  // false - not an own property
marks.hasOwnProperty(0);      // true  - and hasOwnProperty itself is inherited

The difference between in and hasOwnProperty matters the moment you use a plain object as a lookup table. 'toString' in scores is true for an empty object, because toString lives on Object.prototype. Checking membership with in on a dictionary therefore reports keys you never added.

One more rule that catches people: assignment never writes through the chain. u.greet = something does not modify User.prototype.greet. It creates an own property on u that hides the inherited one. Reads travel up the chain, writes always land on the object in front of you.

class is syntax over the same machinery

Java and C++ students often assume class introduced real classes to JavaScript. It did not. A class is still a function, its methods still live on ClassName.prototype, and instances are still linked by [[Prototype]]. Print it and check.

class User {
  constructor(name) { this.name = name; }
  greet() { return 'Hi, ' + this.name; }
}

typeof User;                     // "function"
Object.keys(User.prototype);     // []  - greet exists but is non-enumerable
new User('Riya').greet();        // "Hi, Riya"

extends wires up two chains at once. Instances inherit through the prototypes, and the child constructor itself inherits static members from the parent constructor.

class Student extends User {
  constructor(name, roll) {
    super(name);
    this.roll = roll;
  }
}

const s = new Student('Ananya', 'CS-21');
s.greet();                                                   // "Hi, Ananya"
Object.getPrototypeOf(Student.prototype) === User.prototype;  // true
Object.getPrototypeOf(Student) === User;                      // true

Where class is not merely sugar is in the rules it enforces. Class bodies always run in strict mode. A class cannot be called without new, it throws a TypeError. Class declarations sit in the temporal dead zone, so unlike function declarations you cannot use one before its line. Methods defined in the body are non-enumerable, so they never show up in for...in. And in a derived class you must call super() before touching this.

The strict mode rule produces a real bug. Pull a method off an instance and call it loose, and a prototype method written the old way silently reads the global object, while a class method throws.

const { greet } = new User('Riya');
greet();  // TypeError: Cannot read properties of undefined (reading 'name')

Shadowing, shared state and the for...in leak

Because writes always create own properties, an instance can shadow a prototype method without disturbing anything else. Delete the own property and the inherited one becomes visible again.

const a = new User('Aarav');
const b = new User('Riya');

a.greet = function () { return 'custom'; };
a.greet();  // "custom"   - own property wins
b.greet();  // "Hi, Riya" - untouched

delete a.greet;
a.greet();  // "Hi, Aarav" - back to the prototype method

The reverse is not symmetric. A prototype is a single shared object, so if you put a mutable value on it, every instance shares one copy. Put User.prototype.subjects = [] and one student pushing a subject changes it for all of them. Arrays and objects belong in the constructor, per instance. Only functions and genuinely constant values belong on the prototype.

Defining methods inside the constructor is the other extreme. Each instance gets its own function object, so a.greet !== b.greet, which breaks identity comparisons and creates one closure per instance. Class fields assigned an arrow function behave the same way, which is exactly why an arrow-function handler in a React class component is a new function on every instance.

The most annoying leak is for...in. Properties you assign to a prototype with = are enumerable, and for...in walks the whole chain.

function Product(name) { this.name = name; }
Product.prototype.gst = 0.18;

const p = new Product('Notebook');
for (const key in p) console.log(key);  // name, gst
Object.keys(p);                          // ['name']

Use Object.keys(), Object.entries() or Object.hasOwn(obj, key) when you only want the object's own data. Reserve for...in for the rare case where you genuinely want inherited keys.

What interviewers are really testing

Prototype questions come up in placement interviews because they separate people who memorised syntax from people who understand the object model. The questions are predictable, so prepare the short answers.

Difference between prototype and __proto__? prototype is a property on constructor functions holding the object future instances will inherit from. __proto__ is a legacy accessor for the link an object already has. Prefer Object.getPrototypeOf() in code you write.

How do you inherit without classes? Object.create() takes the prototype directly, no constructor involved.

const base = {
  describe() { return this.name + ', ' + this.city; }
};

const shop = Object.create(base);
shop.name = 'Sharma Traders';
shop.city = 'Pune';

shop.describe();                       // "Sharma Traders, Pune"
Object.getPrototypeOf(shop) === base;  // true

How does instanceof work? It walks the left operand's prototype chain looking for the right operand's prototype object. That is why it fails across boundaries: an array from an iframe is not instanceof your page's Array, and an object built by one copy of a package fails instanceof against a class from a second installed copy. Both are real bugs, not trivia.

Can you change a prototype after creation? Object.setPrototypeOf() exists but engines optimise objects by their fixed shape, and re-pointing the prototype forces that optimised form to be discarded for every object involved. Build the object with the right prototype using Object.create() or new instead.

Finally, know why Object.create(null) exists. It produces an object with no prototype at all, so there is no inherited toString and no __proto__ setter. That makes it a safe dictionary, and it blocks prototype pollution, where a merge function copying a user-supplied __proto__ key writes onto Object.prototype and affects every object in the program.

Frequently Asked Questions

Why is user.prototype undefined? Because instances do not have a prototype property. Only functions do. What you are looking for is the instance's internal prototype link, which you read with Object.getPrototypeOf(user). The value it returns is the same object as User.prototype, the one the constructor handed over when you called it with new.
Is JavaScript class real inheritance or just syntax sugar? The inheritance mechanism underneath is the same prototype chain, so in that sense it is syntax over existing machinery. But class is not purely cosmetic. It forces strict mode, refuses to run without new, sits in the temporal dead zone, makes methods non-enumerable, and requires super() before this in a derived constructor. Those are behavioural rules you cannot reproduce exactly with a plain function.
Should I add methods to built-in prototypes like Array.prototype? No. Adding to a built-in prototype affects every array in the program, including arrays inside libraries you did not write. If two libraries define the same helper differently, the later one wins and the other breaks. Enumerable additions also show up in any for...in loop over an array. Write a standalone function and import it instead.
What is the difference between hasOwnProperty and the in operator? The in operator returns true if the key is found anywhere on the prototype chain, so 'toString' in {} is true. hasOwnProperty checks only the object itself. When you are treating an object as a lookup table, use Object.hasOwn(obj, key) or Object.keys, otherwise inherited names will look like data you stored.
Do prototypes make my code faster or slower? Putting shared methods on a prototype means one function object exists instead of one per instance, which is why it is the normal pattern. Lookups walking a long chain do cost more work than an own property, and engines optimise for objects whose shape and prototype do not change. The practical advice is to keep chains shallow and never re-point a prototype after an object is created.