Given `class Base { static create() { return new this(); } }` and `class Sub extends Base {}`, what does `Sub.create()` return, and why does `Sub` have a `create` method at all?
answer
- two links, not one
- the constructor object has a prototype too
- this is the receiver of the call
- new this() versus new Base()
basics
~20 sSub.create() returns a Sub instance. Sub inherits create because extends makes Base the prototype of the Sub constructor object, so static lookups fall through, and this inside a static is the class it was called on — here Sub.
solid answer
~40 sIt returns an instance of `Sub`. Two separate facts combine. First, `extends` links the constructors themselves: the `Sub` function object's prototype is `Base`, so looking up `Sub.create` misses on `Sub`, walks to `Base`, and finds it — that is how statics are inherited. Second, inside a static method `this` is whatever the method was called on, so `Sub.create()` gives `this === Sub` and `new this()` constructs a `Sub`. That pairing is the standard polymorphic-factory idiom: a base class writes `static create`, `static fromJSON` or `static empty` once and every subclass gets a version that builds its own type. It breaks the moment someone writes `new Base()` instead of `new this()`, or detaches the method — `const f = Sub.create; f()` throws, since `this` is then undefined in strict class code.
code
javascript · 12 linesclass Model {
static create(...args) { return new this(...args); }
static createWrong() { return new Model(); }
}
class User extends Model {}
console.log(Object.getPrototypeOf(User) === Model); // true — statics inherit
console.log(User.create() instanceof User); // true
console.log(User.createWrong() instanceof User); // false — hard-coded name
const detached = User.create;
try { detached(); } catch (e) { console.log(e.constructor.name); } // TypeErrorgo deeper
Know that a subclass can call the parent's static methods, and that this inside such a method refers to a class rather than an instance. Being able to predict that Sub.create() yields a Sub is enough here.
Explain both links extends creates and demonstrate the inherited lookup with Object.getPrototypeOf(Sub) === Base. State the receiver rule for this and contrast new this() with new Base().
Show where the idiom leaks in real code: statics passed as callbacks losing their receiver, inherited static fields silently splitting on assignment, and factories that must deliberately pin to the base. Say how you would catch each in review.
Argue about the API contract: a base class that calls new this(...) is imposing a constructor signature on every subclass forever. Weigh that against an explicit factory registry, and say how you would evolve such a base without breaking downstream subclasses.
## The two answers the question wants `Sub.create()` returns a `Sub` instance, and it does so for two independent reasons that interviewers like to see separated. ## Reason one: statics are inherited through the constructor objects When you write `class Sub extends Base {}`, the language wires up two links, not one. The familiar one is for instances. The second is the one this question turns on: the `Sub` **function object itself** gets `Base` as its prototype. ```js class Base { static create() { return new this(); } } class Sub extends Base {} Object.getPrototypeOf(Sub) === Base; // true Object.getPrototypeOf(Base) === Function.prototype; // true 'create' in Sub; // true Object.getOwnPropertyNames(Sub).includes('create'); // false — inherited, not own ``` So `Sub.create` is an ordinary property lookup on an ordinary object: miss on `Sub`, follow its prototype to `Base`, hit. Statics are not copied down, and they are not special-cased — they are inherited by exactly the same mechanism as any other property. That also means shadowing works normally: declaring `static create()` in `Sub` puts an own property on `Sub`, and the `Base` version is no longer reached (though `super.create()` inside a `Sub` static still calls it). ## Reason two: `this` in a static is the receiver A static method has no instance, but it is still a method call: `this` is the object to the left of the dot. `Base.create()` gives `this === Base`; `Sub.create()` gives `this === Sub` even though the function body physically lives in `Base`. `new this()` therefore constructs whatever class made the call. ```js class Model { static create(...args) { return new this(...args); } static get label() { return this.name; } // Function.prototype.name of the class } class User extends Model {} User.create() instanceof User; // true User.create() instanceof Model; // true — User's instances are Models too User.label; // "User" ``` This is the whole basis of the polymorphic-factory idiom: write the creation logic once on the base and every subclass gets a correctly-typed version for free. ## The bug this idiom exists to prevent Hard-coding the class name is the failure mode: ```js class Base { static createWrong() { return new Base(); } } // always a Base class Sub extends Base {} Sub.createWrong() instanceof Sub; // false — surprising and usually a bug ``` A reviewer's rule of thumb: inside a static, prefer `this` to the class's own name whenever the result is meant to track the subclass. Use the literal name only when you deliberately want the base's version — for example a registry that must stay singular across the hierarchy. ## Where `this` stops being the class Because `this` is just the receiver, anything that changes or removes the receiver changes the answer: ```js const f = Sub.create; f(); // TypeError — class bodies are strict, so this is undefined Base.create.call(Sub); // a Sub — call() sets the receiver explicitly [Sub, Base].map(c => c.create()); // fine: each call has its own receiver [Sub, Base].map(Base.create); // broken: receiver is lost ``` Passing a static method as a callback is the everyday version of this trap, and the fix is the everyday one: wrap it in an arrow function or bind it. ## A caveat on inherited state Inheriting a static **method** is free; inheriting a static **field** is a copy at class-definition time only in the sense that there is no copy at all — the field lives on the class that declared it, and subclasses read the same single property through the chain. So `Sub.counter++` on an inherited `static counter = 0` does not increment the base's field; the `++` reads through the chain and then *writes an own property on `Sub`*, silently splitting the counter in two. If a counter is meant to be shared, write to it through the declaring class by name. ## What to say out loud Name the two links, name the receiver rule, and give the factory as the reason anyone cares. Then mention the two failure modes — hard-coded `new Base()` and a detached method reference — because those are what actually show up in code review.
- How would you check, in the console, that statics really are inherited rather than copied?Compare `Object.getOwnPropertyNames(Sub)` with `'create' in Sub`. The name is absent from Sub's own properties but the `in` check succeeds, which proves the lookup went through the prototype chain. `Object.getPrototypeOf(Sub) === Base` confirms the link directly.
- A base class declares `static count = 0` and a subclass runs `Sub.count++`. What happens to the base's value?It stays at 0. The read finds the inherited property, but the assignment creates a new own property on `Sub`, so the hierarchy now has two independent counters. If the counter must be shared, write through the declaring class by name — `Base.count++` — rather than through `this` or the subclass.
- When is hard-coding the class name inside a static the right choice?When the result must not vary by subclass: a shared registry, a cached singleton, or a sentinel that has to compare equal everywhere. Using `this` there would give each subclass its own copy, which is usually the opposite of the intent.
saying these in an interview costs you the question
- Says statics are copied onto subclasses at definition time
- Claims this in a static always means the declaring class
- Expects new this() to build the base class
- Thinks a detached static keeps its class as this
- Assumes Sub.count++ updates the base's static field