In JavaScript, when a method body contains `super.greet()`, how does the engine decide which object `greet` is looked up on, and why does copying that method onto a different object not change the answer?
answer
- lookup and receiver come from different places
- the method remembers where it was written
- only concise definitions get the slot
- copying the function copies the link
- a three-level chain would loop otherwise
basics
~20 sMethods written with the shorthand syntax carry a hidden link to the object they were defined in, and super looks the name up on that object's prototype — not on the receiver. Copying the method elsewhere keeps the original link.
solid answer
~40 sA method defined with the concise syntax — in a class body or an object literal — carries an internal slot, `[[HomeObject]]`, pointing at the object it was written in. `super.greet` is resolved by taking the prototype of that home object and looking `greet` up there; the found function is then called with the current `this`. Crucially, the *lookup* uses `[[HomeObject]]` while the *receiver* is `this`, so the two can diverge. Assigning the method onto an unrelated object with `Object.assign` or a plain assignment copies the function object, slot and all, so `super` still reaches the original parent even though `this` is now the new object. A method written as `key: function () {}` has no home object at all, and `super` inside it is a `SyntaxError`.
code
javascript · 10 linesconst parent = { greet() { return 'parent'; } };
const child = Object.setPrototypeOf(
{ greet() { return 'child -> ' + super.greet(); } },
parent
);
const other = Object.setPrototypeOf({}, { greet() { return 'other'; } });
other.greet = child.greet;
console.log(child.greet()); // child -> parent
console.log(other.greet()); // child -> parentgo deeper
Know that super.someMethod() calls the parent class's version of a method while this still refers to the current instance.
Explain that concise method definitions carry a hidden home-object link, that super looks up from that object's prototype, and that a function expression assigned to a property cannot use super at all.
Show the divergence in a real scenario — a copied or mixed-in method whose super still points at the donor's parent — and explain why lookup via the receiver would recurse in a three-level hierarchy.
Weigh mixin strategies on this basis: class-factory mixins compose correctly through super, while property-copying helpers quietly break it, which decides how you let library consumers extend your types.
## Two different questions `super.x` has to answer A call like `super.greet()` involves two decisions: *where do I look the property up*, and *what is `this` when I call it*. JavaScript answers them from different sources, and every surprise in this area comes from confusing the two. - **Lookup** starts from the prototype of the method's `[[HomeObject]]`. - **Receiver** is the current `this`, unchanged. That split is deliberate. Using `this` for lookup would be the naive design, and it breaks immediately: a three-level hierarchy where the middle class calls `super.greet()` would find its own method again through `Object.getPrototypeOf(this)` and recurse forever. ## What `[[HomeObject]]` is When the engine evaluates a **concise method definition**, it stamps the resulting function with an internal slot recording the object the method was defined in. Concise means the shorthand form — `greet() {}`, `get x() {}`, `set x(v) {}`, `*gen() {}`, `async run() {}` — inside either a class body or an object literal. For an instance method of a class, the home object is `C.prototype`; for a static method it is the class `C` itself; for an object literal method it is the literal. A function *expression* assigned to a property gets no such slot: ```js const obj = { greet: function () { return super.greet(); } // SyntaxError }; ``` This is rejected at parse time rather than failing at call time, because the parser knows `super` cannot be resolved in that position. ## The consequence: `super` travels with the method Because the slot lives on the function object, it survives being moved: ```js const parent = { greet() { return 'parent'; } }; const child = Object.setPrototypeOf( { greet() { return 'child -> ' + super.greet(); } }, parent ); const other = Object.setPrototypeOf({}, { greet() { return 'other'; } }); other.greet = child.greet; child.greet(); // "child -> parent" other.greet(); // "child -> parent" <- not "child -> other" ``` `other.greet()` runs with `this === other`, so any `this.x` inside would read `other`'s state — but `super.greet` still resolves through `child`, the object the method was written in. `Object.assign(other, child)` behaves identically, because it copies the same function object. This matters for mixin helpers that copy methods between prototypes. Copying a method that uses `super` silently keeps pointing at the donor's parent, so the mixin appears to work until someone changes the donor's hierarchy. Mixins implemented as class factories — a function returning `class extends Base {}` — avoid the problem entirely, because each generated class body has its own home object at the right place in the chain. ## Static methods and getters The same rule applies with a different home object. Inside a static method of `Child`, the home object is `Child`, so `super.create()` looks `create` up on `Object.getPrototypeOf(Child)` — the parent class. Inside an accessor, `super.x` reads or writes through the parent's getter or setter with `this` still bound to the current instance, which is how a subclass accessor can wrap the parent's. ## Arrow functions and nested scopes An arrow function has no `[[HomeObject]]` of its own, and `super` inside it resolves lexically to the enclosing method's: ```js class Child extends Parent { greet() { return [1].map(() => super.greet()); // fine } } ``` A nested ordinary `function` expression is not a method definition, so `super` inside it is a `SyntaxError` even though it sits inside a class body. ## Reassigning prototypes at runtime Because lookup goes through the home object's prototype and that prototype is read at call time, changing it changes where `super` lands: ```js Object.setPrototypeOf(Child.prototype, OtherParent.prototype); ``` After this, every `super.x` in `Child`'s instance methods resolves against `OtherParent.prototype`. The home object is fixed; its prototype is not. This is occasionally used by test doubles and hot-reload machinery, and it is a good reason to treat `setPrototypeOf` on a live prototype as a heavy operation rather than a tweak. ## How to reason about it in an interview State the rule as a sentence: *lookup from the home object's prototype, call with the current `this`*. Then demonstrate the divergence with the copied-method example. That single example proves you understand the slot exists, that it is attached to the function rather than the call site, and why the naive `Object.getPrototypeOf(this)` design fails.
- Why would resolving super through Object.getPrototypeOf(this) instead of the home object be broken?It infinite-loops in a three-level hierarchy. If `Middle.prototype.greet` calls `super.greet()` and `this` is a `Leaf` instance, `Object.getPrototypeOf(this)` is `Leaf.prototype`, whose lookup finds `Middle`'s own `greet` again. Anchoring to the home object makes the starting point depend on where the code was written, so each level moves strictly one step up.
- Can super appear inside an arrow function or a nested function expression within a method?Inside an arrow function, yes — arrows have no home object of their own, so `super` resolves lexically to the enclosing method's, which makes `super.x` work inside callbacks like `map`. Inside a nested ordinary `function` expression, no: it is not a method definition, so `super` there is a `SyntaxError` even within a class body.
- What breaks when a mixin helper copies methods that use super between prototypes?The copied methods keep the donor's `[[HomeObject]]`, so their `super` calls still resolve through the donor's parent rather than the recipient's. The result silently ignores the recipient's hierarchy and breaks the moment the donor's parent changes. Class-factory mixins — a function returning `class extends Base {}` — give each body a correctly placed home object instead.
- Does changing a prototype at runtime affect where existing super calls land?Yes. The home object is fixed when the method is defined, but its prototype is read at call time, so `Object.setPrototypeOf(Child.prototype, Other.prototype)` redirects every `super.x` in `Child`'s methods to `Other.prototype`. That is one reason mutating a live prototype is a heavyweight, deoptimising operation rather than a small tweak.
saying these in an interview costs you the question
- Says super resolves through the prototype of this
- Thinks super rebinds when the method is copied
- Believes super works in any function inside a class body
- Claims super.greet() calls the parent with the parent as this
- Assumes Object.assign-based mixins carry super correctly