Classes: private fields, static members and inheritance, built on prototypes
class is a cleaner way to write constructors and prototypes, plus real private fields. Learn methods, #private, static, getters, extends, super and brand checks.
Comes up in: JavaScript, Recruiter screen, Low-level design
Learn#
In Nisha’s wallet app, a bug elsewhere does account.balance = 999999. Nothing stops it. Underscore names like _balance are only a polite request. She needs data that code outside the class truly cannot reach.
A class gives her that: a private field, written #balance, can only be used inside the class body. Outside, account.#balance is a syntax error.
Most of a class is the same machinery as constructors and prototypes, written more clearly.
The constructor runs on new. Methods go on the prototype and are shared.
A get balance() lets outsiders read without writing.
Static members belong to the class itself, not to instances; a static { } block runs once to set them up.
extends links the prototypes, and super calls the parent’s constructor or methods.
class Account {
#balance = 0;
static #count = 0;
static { Account.bank = "Engrain Bank"; }
constructor(owner) { this.owner = owner; Account.#count++; }
deposit(amount) { this.#balance += amount; return this; }
get balance() { return this.#balance; }
static isAccount(x) { return #balance in x; }
static get count() { return Account.#count; }
}
class Savings extends Account {
constructor(owner, rate) { super(owner); this.rate = rate; }
addInterest() { return this.deposit(this.balance * this.rate); }
}
const s = new Savings("Asha", 0.1).deposit(1000).addInterest();
console.log(s.balance, Account.isAccount(s), Account.isAccount({ balance: 5 }));
console.log(Object.keys(s), Account.count, Account.bank);Output (verified)
1100 true false [ 'owner', 'rate' ] 1 Engrain Bank
#balance in x is a brand check: true only for objects the class really created.
constructors running
- Savings
object s
- (not created yet)
new Savings('Asha', 0.1): Savings must call super() before it can use this.
Read the steps as text
- new Savings('Asha', 0.1): Savings must call super() before it can use this.
- super('Asha') runs Account's constructor. The object is created here, with its private #balance = 0.
- Back in Savings, this is ready. rate is added.
- deposit and addInterest live on the prototypes, shared by every account. Only they can touch #balance.
Predict#
Predict first
class A { #x = 1; static read(o) { return o.#x; } } — what does A.read({}) do?
Reading a private field on an object that does not have it throws a TypeError — unlike normal properties, which give undefined. That is why #x in obj exists: to check safely first.
Interview angle#
Say it like this — about 30 seconds
“Classes are mostly a clearer syntax for constructor functions and prototypes: methods go on the prototype,
extendslinks prototypes, andsupercalls the parent. But some parts are new. Classes must be called withnew, their bodies are strict, and#privatefields are enforced by the engine — not visible toObject.keys,JSON.stringifyor proxies. Unlike closures, private fields are shared by methods on the prototype, and#x in objgives a reliable brand check.”
Follow-up How do private fields differ from closures or _x names?
_x is only a naming convention. Closures hide data but need per-instance functions. #x is hidden by the engine and still lets shared prototype methods use it.Follow-up Why must a subclass call super() before using this?
super() returns, there is no this to use, so accessing it throws a ReferenceError.Follow-up What is a static member for?
Account.fromJSON, or brand checks.Practice#
On your own
Rewrite BankAccount with a private #balance, a read-only balance getter, a guard in withdraw, and a real brand check.
Done when
depositandwithdrawreturn the new balance; withdrawing too much throws.acc.balance = 1e6changes nothing, and the balance does not appear inJSON.stringify(acc).BankAccount.isAccount(acc)is true; a look-alike object is false.
Remember#
Try to answer each card before you open it. Then add them to your reviews.
Where do class methods live?
On the class's prototype, shared by all instances.
What makes #private fields different from _names?
The engine enforces them; code outside the class cannot read or write them.
What does #x in obj check?
Whether obj was created with the class's private field (a brand check).
When must a subclass call super()?
Before using this in its constructor.
Go deeper
Four layers. Stop at the one your interview needs; each ends with a quick check.
The core ideaEvery round
Constructor sets up each instance; methods are shared on the prototype; #private data is invisible outside; static belongs to the class; extends/super link parent and child.
Check yourself: Does new Savings() have its own copy of deposit()?
No. deposit lives on Account.prototype and is shared.
How it works underneathSenior rounds
Private fields are not properties. Each object keeps them in an internal [[PrivateElements]] list, keyed by a unique Private Name that only code inside the class body can mention Source: ECMA-262: PrivateElement. So Object.keys, in (without #) and proxies never see them.
Check yourself: Where are private fields stored?
In an internal list on each object ([[PrivateElements]]), not as properties.
Edge cases and trapsWhere people slip
Calling a class without new throws. Class declarations are in the TDZ before their line. Methods are not enumerable. Private names are per class: a subclass cannot read its parent’s #balance Source: MDN: Private properties.
Check yourself: Can you call a class without new?
No. It throws a TypeError.
In productionStaff-level answers
Keep inheritance shallow — one level is usually enough — and prefer composition (JS.04.04). Use private fields for invariants like balances. In TypeScript, private is only a compile-time check; #private is enforced at run time (TS.04.04).
Check yourself: Why prefer composition over deep class trees?
Deep inheritance couples classes tightly and makes changes risky; composing small parts is easier to change.
Finished the five steps? Mark it, and its cards join your reviews.
Last checked 2026-10-31. Spot a mistake? Use “Report an error” below — fixes are listed on the corrections page.
Interview questions this answers
- How do private fields (
#x) differ from closures or_xconventions?