`public`, `private`, `protected` — and `#` for Real Privacy
Access Modifiers
TS adds access modifiers to control which members are reachable from outside the class. Plus JS's `#` for actual runtime privacy.
What you'll learn
- Use `public` / `private` / `protected`
- Know that TS `private` is a compile-time check
- Use `#field` for runtime-private state
By default, every class member is public — reachable from
anywhere. TS adds modifiers to restrict access.
public (Default)
class User {
public name: string; // same as: name: string
constructor(name: string) { this.name = name; }
} You rarely write public — it’s the default.
private — Same Class Only
class Counter {
private count = 0;
increment() { this.count++; }
value() { return this.count; }
}
const c = new Counter();
c.increment();
c.value(); // ✓
c.count; // ✗ Property 'count' is private private is a compile-time check. Nothing stops a JS caller
from poking at c.count at runtime — it’s erased.
protected — Subclass-Accessible
class Animal {
protected name: string;
constructor(name: string) { this.name = name; }
}
class Dog extends Animal {
bark() { return `${this.name} says woof`; } // ✓ subclass can read
}
const d = new Dog("Rex");
d.name; // ✗ protected — not reachable outside the class hierarchy Like private, but subclasses also get access.
readonly Combo
class User {
constructor(public readonly id: string) {}
} Public — but can’t be reassigned after construction.
#field — Real Privacy (JS Feature)
ECMAScript shipped #field for true runtime privacy. TS supports it:
class Counter {
#count = 0;
increment() { this.#count++; }
value() { return this.#count; }
}
const c = new Counter();
c.increment();
c.value(); // ✓
c.#count; // ✗ SyntaxError — and undetectable at runtime Difference from private:
| Modifier | Compile check | Runtime check |
|---|---|---|
private | Yes | No (erased) |
#field | Yes | Yes (real privacy) |
For new code: prefer #field. It’s a JS feature, not a TS-only
thing, and it actually hides the state. Use private when you
need to interop with code that expects field names without # —
or with older targets.
Up Next
Abstract classes — half-implemented base classes that force subclasses to fill in the gaps.
Abstract Classes →