Detailed explanation
Prototypal inheritance is JavaScript’s mechanism for letting one object access properties and methods defined on another object. Every ordinary object has an internal [[Prototype]] link whose value is another object or null. When you read a property, JavaScript first checks the object itself. If the property is absent, it follows the prototype link and repeats the search. This prototype chain ends at null; if no matching property exists, the lookup returns undefined. Inheritance therefore works through delegation rather than copying properties into each object.
In the example, calling new Animal('Rex') creates an object linked to Animal.prototype, runs Animal with that new object as this, and returns the object because the constructor does not return a replacement object. The assignment this.name = name creates an own property on d. However, speak belongs to Animal.prototype, so d.speak resolves through the prototype chain. Calling it as d.speak() makes this refer to d, allowing the shared method to read d.name and return 'Rex speaks'. Multiple Animal instances can reuse that same function while maintaining separate name values.
A constructor’s .prototype property and an instance’s internal [[Prototype]] are different concepts. Animal.prototype is the object that new Animal() uses as the new instance’s prototype; Object.getPrototypeOf(d) exposes that internal link. Object.create(proto) establishes a prototype link directly without invoking a constructor. JavaScript class syntax also uses prototypes for instance methods, although classes introduce additional semantics and restrictions. For ordinary objects, the chain commonly reaches Object.prototype and then null, but objects created with Object.create(null) have no inherited Object.prototype properties.
This matters for method sharing, property ownership, and predictable debugging. An own property shadows an inherited property with the same name; deleting that own property can reveal the inherited value again. Changes to a shared prototype can affect instances that delegate to it. Interviewers typically check whether you can trace a lookup, distinguish own from inherited properties, explain this in a method call, and avoid confusing inheritance with property copying.
Key points to remember
- Property reads search the object first, then its prototype chain, stopping at a match or null.
- Prototype methods are shared; constructor assignments such as this.name create instance-specific properties.
- Animal.prototype is a constructor property; Object.getPrototypeOf(d) returns d’s internal prototype.
- An own property shadows an inherited property without changing the prototype’s property.
- Use Object.create() to establish delegation directly and Object.hasOwn() to check property ownership.
Common mistakes candidates make
- Saying inherited properties are copied into every instance rather than found through delegation.
- Confusing Animal.prototype with Animal’s own internal prototype or assuming d.prototype holds d’s prototype link.
- Assuming this always refers to the prototype where a method is stored; for d.speak(), it refers to d.
Likely follow-up questions
What is the difference between Object.hasOwn() and the in operator?
Object.hasOwn(obj, key) checks only properties belonging directly to obj. The in operator checks both own and inherited properties, regardless of whether their value is undefined.
What happens if you assign d.speak a new function?
In this example, the assignment creates an own speak property that shadows Animal.prototype.speak. Other instances still use the prototype method, and deleting d.speak exposes the inherited method again.
How does class inheritance relate to prototypes?
With class Dog extends Animal, Dog.prototype delegates to Animal.prototype, so Dog instances can find Animal’s instance methods through the chain. Static inheritance uses a separate link from the Dog constructor to the Animal constructor.
Interview tip: Trace d.speak() from the instance to Animal.prototype, then explain why the shared function still reads the instance’s own name.
