Private class properties
A private property is a class property whose name starts with #: it is reachable only inside the class itself and is completely unavailable, even invisible, from the outside. Unlike the old underscore convention, this is a language level guarantee rather than an agreement between developers.
Theory
TL;DR
- Syntax:
#fieldin the class body, accessed asthis.#field. - From outside,
obj.#fieldis aSyntaxError, notundefined. - Fields, methods and static members can all be private (
static #token). - Private fields are not inherited: a subclass cannot see the parent's private fields.
- They do not appear in
for...in,Object.keysorJSON.stringify. - A private field name cannot be computed dynamically, it is always written literally in the code.
Quick example
class User {
#password = '12345'; // a private property
checkPassword(pwd) {
return pwd === this.#password;
}
}
const u = new User();
console.log(u.#password); // SyntaxError, no direct access
console.log(u.password); // undefined
console.log(u.checkPassword('12345')); // trueHere:
#passwordis a private property,- it is reachable only inside the class's own methods,
- from outside, an access attempt raises a syntax error, not merely
undefined.
Private fields and methods
You can hide methods as well as properties:
class Account {
#balance = 0;
#log(message) {
console.log('[LOG]:', message);
}
deposit(amount) {
this.#balance += amount;
this.#log(`Balance topped up by ${amount}`);
}
getBalance() {
return this.#balance;
}
}
const acc = new Account();
acc.deposit(100);
// acc.#log('test'); // Error, it is a private method
console.log(acc.getBalance()); // 100Private methods are handy for helper logic that the class's users should never see.
How this differs from regular properties:
| Property | Visibility | Changeable from outside? | Enumerable in for...in? |
|---|---|---|---|
this.value | Public | Yes | Yes |
#value | Private | No | No |
Why the underscore convention is not enough
Before # existed, developers imitated privacy by convention, prefixing the name with _:
class User {
_password = '12345';
}But that protects nothing:
const u = new User();
u._password = 'hacked'; // changed without any resistance# is real protection at the language level, not just a team agreement.
Inheritance and private statics
Private fields are not inherited and are not available in subclasses:
class Parent {
#secret = 'parent secret';
reveal() {
console.log(this.#secret);
}
}
class Child extends Parent {
test() {
console.log(this.#secret); // Error, it is the parent's private field
}
}Every class sees only its own private fields, even when it extends another one.
Static members can be private too:
class Config {
static #token = 'abc123';
static getToken() {
return this.#token;
}
}
console.log(Config.getToken()); // 'abc123'
console.log(Config.#token); // ErrorThis is useful when class level confidential data must stay hidden.
A real world example and the limits
The private #balance field protects the data from direct tampering:
class BankAccount {
#balance = 0;
deposit(amount) {
if (amount <= 0) throw new Error('The amount must be positive');
this.#balance += amount;
}
withdraw(amount) {
if (amount > this.#balance) throw new Error('Insufficient funds');
this.#balance -= amount;
}
getBalance() {
return this.#balance;
}
}
const acc = new BankAccount();
acc.deposit(500);
console.log(acc.getBalance()); // 500
acc.withdraw(200);
console.log(acc.getBalance()); // 300
// acc.#balance = 100000; // Error, it cannot be changed from outsideOne important limitation: a private property cannot be created dynamically. Private field names are fixed in the source:
const name = '#secret';
class A {
[name] = 123; // Error, a variable cannot be used here
}This is deliberate, it keeps the guarantee airtight: the field is reachable only through a literal # name.
Summary table:
| Feature | Private property (#field) |
|---|---|
| Visibility | Only inside the class |
| Declaration | With #name |
| Access from outside | An error, not just undefined |
| Inheritance | Not passed to subclasses |
| Allowed on statics | Yes (static #secret) |
| Works with getters and setters | Yes |
| Can be created dynamically | No |
Common mistakes
- Confusing
#fieldwith_field. The underscore is only a convention, any code can overwrite such a field. - Expecting
undefinedfrom outside. Touchingobj.#fieldoutside the class fails at parse time, so it breaks the whole file, not just that line. - Counting on subclass access. A parent's private fields are invisible in a subclass; if you need access, use a protected style pattern with a regular field or a getter.
- Using
incarelessly for feature detection. The#field in objcheck does work, but only inside the class that declares the field. - Forgetting that private fields are not serialized.
JSON.stringify(acc)will not show#balance, so persisting the state needs an explicit method.
Short Answer
Interview readyA concise answer to help you respond confidently on this topic during an interview.