Articles
JavaScript Prototype Interview Questions: Lookup, Inheritance, and Class Semantics
Practice JavaScript prototype interview questions with property lookup traces, shadowing, class methods, constructor behavior, and inheritance pitfalls.
JavaScript prototype interview questions ask you to trace where a property comes from, what an assignment changes, and how class syntax relates to object inheritance. The core rule is simple: a property read checks the object, then follows its prototype chain until it finds a matching property or reaches null.
The surprising behavior comes from mixing that lookup rule with own properties, shared mutable values, getters, and class fields. This guide works through small examples you can predict on paper before running them. It focuses on lookup and inheritance rather than turning into a general guide to closures or this.
For each snippet here, identify the receiver, its own properties, and its prototype before predicting the output. That approach is more reliable than memorizing isolated console results.
How does JavaScript property lookup work?
Consider a deliberately small object chain:
const base = { label: "shared", count: 1 };
const item = Object.create(base);
item.name = "report";
console.log(item.name); // report
console.log(item.label); // shared
console.log(item.absent); // undefined
name is an own property of item. label is not, so lookup continues to base. An absent property is searched farther up the chain; if no property is found before the chain ends, an ordinary read produces undefined.
Be careful: a property can exist and contain undefined. Reading the value alone cannot distinguish that case from a missing property. Use Object.hasOwn(item, "key") to ask about an own property and the in operator to ask whether a property exists anywhere on the chain.
An interviewer may change base.label after item is created. If item still relies on the inherited property, its next read sees the updated inherited value. Object.create did not copy all of the prototype's properties into the new object.
What is property shadowing?
With an ordinary writable inherited data property, assigning the same key on a child normally creates an own property on that child. The child's value then hides the inherited value during reads.
const palette = { color: "blue" };
const a = Object.create(palette);
const b = Object.create(palette);
a.color = "red";
console.log(a.color, b.color, palette.color);
// red blue blue
delete a.color;
console.log(a.color); // blue
Deleting the own property exposes the inherited value again. It does not create a new copy or restore a remembered previous value. That distinction matters when debugging configuration objects whose defaults live on a prototype.
The “assignment creates an own property” rule needs its assumptions. An inherited setter can handle the assignment instead. A non-writable inherited property can prevent it, and strict mode can affect whether failure throws. A strong answer explicitly says the example uses an ordinary writable data property.
Why can one object change another object's array?
The inherited value itself may be a reference to a mutable object. A property assignment and a mutation through that reference are different operations.
const shared = { tags: [] };
const first = Object.create(shared);
const second = Object.create(shared);
first.tags.push("urgent");
console.log(second.tags); // ["urgent"]
first.tags = [];
first.tags.push("private");
console.log(second.tags); // ["urgent"]
The first push reads the inherited array and mutates that one array. The later assignment gives first its own array, so subsequent mutation affects a different object. No mysterious copying or cross-object synchronization is involved.
If each instance should own its own collection, initialize that collection per instance. If the collection is intentionally shared, make that choice explicit and consider who may mutate it. An inherited array is a poor default for independent per-user or per-request state.
How are a constructor's prototype and an object's prototype different?
The wording is confusing because two related concepts use the same name. An object's internal prototype is the object consulted during inherited property lookup. A constructor function's .prototype property is ordinarily the object assigned as the internal prototype of instances created with new.
function Account(name) {
this.name = name;
}
Account.prototype.describe = function () {
return `Account: ${this.name}`;
};
const account = new Account("Mina");
console.log(Object.getPrototypeOf(account) === Account.prototype);
// true
The Account function is itself an object and has its own internal prototype too. That separate relationship is not what the expression Account.prototype means. Drawing two arrows with labels prevents a common interview mix-up.
Also distinguish changing properties on the existing Account.prototype object from replacing Account.prototype with a different object. Existing instances retain their current prototype object unless it is explicitly changed; replacement affects the ordinary construction path for later instances.
What does new do in an interview explanation?
For an ordinary constructible function, a useful high-level explanation is that new creates an object with the appropriate prototype, invokes the constructor with that object as this, and determines the result according to the constructor's return behavior. A returned object can replace the newly created instance; an ordinary primitive return does not have the same effect.
Avoid presenting a homemade three-line new implementation as a complete language specification. Classes, derived constructors, and other built-in behavior introduce additional rules. The interview goal is normally to explain instance initialization and prototype linkage for the supplied example.
Try this follow-up: add a method to Account.prototype after an account already exists. The existing account can find it because lookup follows the same prototype object. Then replace the whole prototype and construct another account. The two accounts can now have different lookup chains.
This is also why inspecting the actual chain is more reliable than inferring it from a variable's name or its constructor property. That property can be changed and is not an unforgeable statement of identity.
Are JavaScript classes just copied methods?
No. Ordinary instance methods declared in a class body are installed on the class prototype, while instance fields are created on each instance. Class syntax also brings specific rules, including strict-mode behavior and restrictions on calling a class constructor without new.
class Counter {
value = 0;
increment() { this.value += 1; }
reset = () => { this.value = 0; };
}
const x = new Counter();
const y = new Counter();
console.log(x.increment === y.increment); // true
console.log(x.reset === y.reset); // false
The shared prototype method and per-instance arrow-function field have different ownership. That can matter for function identity, memory use, overrides, and callback behavior. Do not summarize the difference solely as “arrow functions are better for classes.” The right choice depends on the contract.
If every object needs independent mutable state, an instance field fits. If all instances use the same ordinary behavior, a prototype method is a natural default. Test the actual callback use when method extraction or rebinding is part of the question.
What does extends connect?
For ordinary class inheritance, extends establishes relationships for both instance method lookup and constructor-level inheritance. A derived instance can look through the derived prototype to the base prototype. Static members follow the constructor inheritance chain rather than living on every instance.
| Member | Typical location | Read through |
|---|---|---|
| Instance field | The instance | instance.field |
| Instance method | Class prototype | instance.method |
| Static method | Constructor object | Class.method |
| Private field | Class-defined private element | Only authorized class syntax |
In a derived constructor, use super according to the class construction rules before accessing this. A base constructor may establish required state, so skipping its contract is more than a syntactic issue.
super.method() also deserves precision: method lookup begins from the relevant parent relationship, while the call still operates with the current receiver. It does not simply mean “call a method on a separate parent instance.” This distinction explains why a base method can read derived instance state.
Which reflection tools should you use?
Use Object.getPrototypeOf to inspect the next prototype and Object.hasOwn to check an own property. Object.keys lists enumerable own string-keyed properties, while for...in can include enumerable inherited string-keyed properties. Symbols and non-enumerable members require the appropriate reflection methods.
That difference is a frequent debugging exercise. A loop over an object unexpectedly includes a field added to its prototype. Decide whether the algorithm intended to process the object's own data or the full inherited interface. Pick a method whose contract matches the answer.
For a dictionary without inherited object properties, Object.create(null) can be useful. Such an object does not inherit ordinary helpers such as hasOwnProperty, which is another reason Object.hasOwn is convenient. A Map may be more appropriate when keys are arbitrary values or map semantics fit the task better.
Avoid casually modifying built-in prototypes or changing the prototype of hot objects during normal processing. Those changes can make shared behavior surprising and can affect optimization. For interview code, prefer explicit object construction and small, inspectable chains.
How do you solve an output-prediction exercise?
Write a property table for every object before running the snippet. Mark each value as an own data property, inherited data property, accessor, or shared object reference. Then process statements one at a time, updating only the object or referenced value actually affected.
For example, begin with two objects inheriting {count: 0, values: []}. Execute a.count++, then a.values.push(1). The increment reads an inherited number and writes an own result in the ordinary case. The push mutates the inherited array. The two lines look similarly instance-focused but affect different state.
Next delete a.count and assign a.values = []. Explain why the count now comes from the prototype again while the values no longer share the original array. This four-line exercise tests lookup, shadowing, deletion, and reference mutation together.
What should you say in a strong interview answer?
Start with the lookup path and state assumptions about descriptors. Then distinguish a property write from a mutation of the value it references. If classes appear, identify which members live on the instance, prototype, or constructor.
Use a concrete sentence such as: “Both instances initially read the same inherited array, so pushing through either reference changes that array; assigning a new array creates an independent own property in this example.” The explanation connects the output to the language model instead of treating it as a trick.
Practice with one accessor example and one class-field example after the basic chain feels easy. Those are natural follow-ups because they test whether you understood the assumptions behind the first answer.
How can you debug inherited state in a real component?
Suppose a settings panel unexpectedly shows an option selected in another panel. First inspect whether each instance owns its settings property with Object.hasOwn(panel, "settings"). Then compare the two settings references with strict equality. If both instances read the same inherited object, changing one object's contents explains the apparent leak without any event-handler bug.
Fix the ownership model rather than hiding the symptom with a reset. Initialize mutable per-instance settings in the constructor or an instance field. Keep shared prototype methods for behavior that should be reused. Finally, create two instances in a test, change one setting, and assert that the other instance remains unchanged. This regression test documents the intended boundary between shared behavior and independent state.
Sources and Further Reading
- MDN: inheritance and the prototype chain
- MDN: JavaScript classes
- MDN: Object.hasOwn
- MDN: new operator
- MDN: public class fields
Research checked September 24, 2026. Examples assume ordinary objects and the descriptors stated in the text; proxies and exotic objects can add behavior.