JavaScript Private Class Fields

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.

4 min read Level 3/5 #classes#private-fields#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.

Private fields script.js
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
▶ Preview: console

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.

A private method script.js
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
▶ Preview: console

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.

in check script.js
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
▶ Preview: console

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 →