Real Privacy With
JavaScript Private Class Fields
Class fields prefixed with `#` are truly private — accessible only inside the class. JavaScript finally has built-in encapsulation.
What you'll learn
- Declare private fields with `#`
- Know that private fields throw on outside access
- Distinguish private fields from closures and conventions
A class field prefixed with # is private — it can only be
accessed from inside the class. This is real, enforced privacy
(ES2022), not a convention.
class BankAccount {
#balance = 0; // private field
deposit(amount) {
this.#balance += amount;
}
withdraw(amount) {
if (amount > this.#balance) throw new Error("insufficient funds");
this.#balance -= amount;
}
getBalance() {
return this.#balance;
}
}
const acct = new BankAccount();
acct.deposit(100);
acct.withdraw(30);
console.log(acct.getBalance()); // 70
// console.log(acct.#balance); // SyntaxError: outside the class The # is part of the name — #balance and balance are different
properties. You declare and reference them with the #.
Private Methods
You can mark methods private too.
class User {
#name;
constructor(name) {
this.#name = name;
}
greet() {
return `Hello, ${this.#format(this.#name)}`;
}
#format(name) {
return name.toUpperCase();
}
}
const ada = new User("ada");
console.log(ada.greet()); // "Hello, ADA"
// ada.#format("anyone"); // SyntaxError The Old Convention: _
Before private fields existed, the convention was to prefix
“private” names with an underscore (_balance). It was a hint,
not a constraint — anyone could still access the property.
class OldBankAccount {
constructor() { this._balance = 0; } // _ means "don't touch" by convention
}
const acct = new OldBankAccount();
acct._balance = 9_999_999; // still works — convention only # makes this real. In new code, use #.
Private Fields vs Closures
Before #, closures were the only way to get true privacy in
JavaScript. They still work, especially when you don’t want a
class at all:
function makeAccount() {
let balance = 0;
return {
deposit: (n) => (balance += n),
getBalance: () => balance,
};
} When you want a class, private fields are cleaner. When you don’t need a class, closures are fine.
A Quick Check: in With Private Fields
You can ask whether an object has a particular private field with
#name in obj. This only works inside the class.
class User {
#name;
constructor(name) { this.#name = name; }
static isUser(obj) {
return #name in obj; // ← branding check
}
}
const a = new User("Ada");
console.log(User.isUser(a)); // true
console.log(User.isUser({ name: "Ada" })); // false This is a “branding” pattern — reliably detecting “is this an instance of MY class” even across realms.
Up Next
Class-level (not instance-level) properties and methods.
JavaScript Static Members →