C.W.K.
Stream
Lesson 02 of 06 · published

`public`, `private`, `protected`, `#private`

~9 min · classes, access-modifiers, encapsulation

Level 0Curious
0 XP0/93 lessons0/23 achievements
0/100 XP to next level100 XP to go0% complete
"Four ways to say 'this is mine.' Three of them are TypeScript-only; one is JavaScript."

The four modifiers

  • public (default): accessible from anywhere. You rarely write this explicitly because it's the default.
  • protected: accessible from this class and its subclasses, not from outside.
  • private (TypeScript): TypeScript-only privacy — compile-time enforced. At runtime, the property is a regular property accessible via obj['name'].
  • #private (JavaScript): true private syntax. Runtime-enforced via hidden slots. Attempts to access from outside are SyntaxErrors at parse time or TypeErrors at runtime.

Which to use

For new TypeScript code: prefer #private for truly private fields, especially anything sensitive (auth tokens, internal handles, raw memory). It can't be bypassed.

For existing code: private is fine — the compile-time contract catches most legitimate access mistakes. Migrating to #private is mechanical when you want stronger guarantees.

For inheritance chains: use protected for fields/methods that subclasses need but external callers shouldn't see. This is a common pattern in framework base classes.

The defaults that work: Most fields should be private (or #private). Most methods should be public. protected is the rare middle ground. Public-by-default-everywhere is too leaky; private-by-default-everywhere is too restrictive.

Code

Four modifiers in action·typescript
class BankAccount {
  // TypeScript private — compile-time only.
  private _balance: number = 0;

  // JavaScript private — runtime enforced.
  #internalToken: string = '';

  // Public method.
  deposit(amount: number): void {
    this._balance += amount;
  }

  // Public accessor for read-only access.
  get balance(): number {
    return this._balance;
  }

  // Protected — subclasses can use it.
  protected validateAmount(amount: number): boolean {
    return amount > 0 && amount < 1_000_000;
  }
}

class SavingsAccount extends BankAccount {
  applyInterest(rate: number): void {
    if (this.validateAmount(rate)) {     // ✅ protected access from subclass
      // can't access this._balance directly — that's parent-private
      this.deposit(this.balance * rate);
    }
  }
}

const acc = new BankAccount();
acc.balance;                     // ✅ public getter
// acc._balance;                 // ❌ private
// acc.#internalToken;           // ❌ SyntaxError — # only inside the class
// acc['_balance']               // ⚠️ accesses TypeScript-private at runtime

External links

Exercise

Write a PasswordVault class with #password (truly private) and addPassword(p: string) and match(p: string): boolean methods. Verify that vault['#password'] at runtime can't read the password, and that subclasses can't access it either (try extending and notice the error).
Hint
#private fields are accessible only from within the class — not from outside, not from subclasses. That's the runtime guarantee that compile-only private doesn't give.

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue

Comments 0

🔔 Reply notifications (sign in)
Sign inPlease sign in to comment.

No comments yet — be the first.