Lesson 46 of 80
Instance fields live on each object; static members live on the class itself — one shared copy, not one per instance.
12 min read
Every option explains itself — why the right one is right, and why each wrong one is wrong. Miss one and it comes back later on a spaced-repetition schedule.
This topic is about judgment and practical tradeoffs more than a single right answer — a mock interview is the better way to test yourself on it.
Be interviewed on this →Hints without spoilers, and a plain explanation of why a wrong answer was wrong — answered only from this lesson. Free account, no card.
Sign up freeWhat interviewers actually ask about this, and what a strong answer has to cover.
A strong answer covers
Likely follow-ups
Everything you put on this inside a constructor exists per instance — every new Widget() gets its own copy. A static member is different: it belongs to the class object itself. There's exactly one of it, no matter how many instances you create, and you reach it through the class name (Widget.count), not through an instance (widget.count doesn't see it).
A static initialization block — static { ... } — is a chunk of code that runs once, at the moment the class is defined, before any instance exists. It exists because a single field initializer expression can't hold multi-line setup logic, a loop, or a try/catch, and a static block can.
A class that hands out sequential IDs and keeps a lookup table of every instance ever created — the counter and the table have to be shared, not duplicated per instance:
class Widget {
static #registry = new Map();
static #nextId = 1;
static {
// runs once, when the class itself is being defined
console.log("Widget class initialized");
}
#id;
constructor(name) {
this.#id = Widget.#nextId++;
Widget.#registry.set(this.#id, name);
}
get id() {
return this.#id;
}
static find(id) {
return Widget.#registry.get(id);
}
static get count() {
return Widget.#registry.size;
}
}
const a = new Widget("sidebar");
const b = new Widget("header");
Widget.count; // 2
Widget.find(a.id); // "sidebar"
a.count; // undefined — count lives on the class, not the instance
#registry and #nextId are static and private — only code inside Widget can touch them directly. Every instance shares the same map through the class, which is exactly the point: a per-instance counter couldn't count across instances.
class Gadget extends Widget {} gets Gadget.find and Gadget.count for free, inherited from Widget.this inside a static method is the class, not an instance. static find(id) { return this.#registry.get(id); } works and is more reusable than hardcoding Widget — if a subclass calls Gadget.find(...), this is Gadget, which matters if the subclass overrides other statics.The prototypes lesson draws a line between an object's own properties and the shared prototype every instance reaches through. Static members are that same split moved up one level: they live on the constructor function itself, a completely different object from Widget.prototype, which is why an instance's method lookup never stumbles onto them by accident.