Skip to main content

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: #field in the class body, accessed as this.#field.
  • From outside, obj.#field is a SyntaxError, not undefined.
  • 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.keys or JSON.stringify.
  • A private field name cannot be computed dynamically, it is always written literally in the code.

Quick example

javascript
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')); // true

Here:

  • #password is 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:

javascript
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()); // 100

Private methods are handy for helper logic that the class's users should never see.

How this differs from regular properties:

PropertyVisibilityChangeable from outside?Enumerable in for...in?
this.valuePublicYesYes
#valuePrivateNoNo

Why the underscore convention is not enough

Before # existed, developers imitated privacy by convention, prefixing the name with _:

javascript
class User { _password = '12345'; }

But that protects nothing:

javascript
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:

javascript
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:

javascript
class Config { static #token = 'abc123'; static getToken() { return this.#token; } } console.log(Config.getToken()); // 'abc123' console.log(Config.#token); // Error

This 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:

javascript
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 outside

One important limitation: a private property cannot be created dynamically. Private field names are fixed in the source:

javascript
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:

FeaturePrivate property (#field)
VisibilityOnly inside the class
DeclarationWith #name
Access from outsideAn error, not just undefined
InheritanceNot passed to subclasses
Allowed on staticsYes (static #secret)
Works with getters and settersYes
Can be created dynamicallyNo

Common mistakes

  • Confusing #field with _field. The underscore is only a convention, any code can overwrite such a field.
  • Expecting undefined from outside. Touching obj.#field outside 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 in carelessly for feature detection. The #field in obj check 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 ready
Premium

A concise answer to help you respond confidently on this topic during an interview.