Lesson 15 of 25
Classes and Access Modifiers
public, private, and protected members, readonly fields, parameter properties, and what TypeScript's private actually protects compared to JavaScript's own
Classes in TypeScript are ordinary JavaScript classes with type annotations and access control layered on top. If you already know JavaScript classes, the syntax here should feel familiar — what's new is what the compiler enforces around it.
public, private, and protected
Three visibility modifiers control where a member can be accessed from:
class BankAccount {
public accountHolder: string; // accessible from anywhere (the default)
private balance: number; // only inside BankAccount itself
protected accountNumber: string; // BankAccount and its subclasses
constructor(accountHolder: string, accountNumber: string) {
this.accountHolder = accountHolder;
this.balance = 0;
this.accountNumber = accountNumber;
}
deposit(amount: number) {
this.balance += amount; // fine — inside the declaring class
}
}
const account = new BankAccount("Amara", "123");
account.accountHolder; // fine — public
account.balance;
// Property 'balance' is private and only accessible within class 'BankAccount'.With no modifier written at all, a member is public by default — the same
as writing it explicitly. private and protected must be opted into.
private vs protected in a subclass
The distinction only shows up once inheritance is involved:
class SavingsAccount extends BankAccount {
addInterest() {
this.accountNumber; // fine — protected reaches subclasses
this.balance;
// Property 'balance' is private and only accessible within class 'BankAccount'.
}
}protected exists specifically for this case: fields a subclass needs to
build on, but that outside code still shouldn't touch directly. private
is stricter — not even a subclass gets access, only the exact class that
declared it.
Compile-time only, same rule as before
This is worth stating plainly, because it surprises people coming from
languages where privacy is enforced by the runtime: TypeScript's private
and protected are erased during compilation, exactly like every other
annotation covered so far. The resulting JavaScript object has an ordinary,
fully accessible property:
// After compiling, BankAccount's "private" balance is just a normal property.
const account = new BankAccount("Amara", "123");
(account as any).balance; // reachable at runtime — the type system just won't let you write this without the castIf you need privacy actually enforced at runtime — not just at compile
time — JavaScript's own # private field syntax is the real mechanism, and
TypeScript fully supports it:
class BankAccount {
#balance = 0; // genuinely private at runtime, not just at compile time
deposit(amount: number) {
this.#balance += amount;
}
}#balance cannot be accessed from outside the class under any
circumstances, cast or no cast, because the JavaScript engine itself
enforces it — not the type checker.
readonly fields
Same meaning as readonly on an interface property: assignable once (at
declaration or in the constructor), then locked, at compile time:
class Lesson {
readonly slug: string;
constructor(slug: string) {
this.slug = slug; // fine — still inside the constructor
}
rename(newSlug: string) {
this.slug = newSlug;
// Cannot assign to 'slug' because it is a read-only property.
}
}Parameter properties
Writing a visibility modifier directly on a constructor parameter is shorthand that both declares the field and assigns it, in one line:
class Lesson {
constructor(
public title: string,
private minutes: number,
readonly slug: string
) {}
}is exactly equivalent to:
class Lesson {
public title: string;
private minutes: number;
readonly slug: string;
constructor(title: string, minutes: number, slug: string) {
this.title = title;
this.minutes = minutes;
this.slug = slug;
}
}Parameter properties are extremely common in real code for exactly this reason — a class with several constructor-assigned fields shrinks considerably, without changing what the class actually does.
Try it yourself
What to remember
- Members are public by default; private restricts access to the declaring class only, protected extends that access to subclasses.
- TypeScript's private and protected are compile-time only and are erased — genuine runtime privacy needs JavaScript's own
#fieldsyntax. - readonly on a class field works the same as on an interface: assignable once, then locked, at compile time.
- A visibility modifier on a constructor parameter (a parameter property) declares and assigns a field in one line.
Check yourself
4 questions · pass 3/4 to unlock Abstract Classes and Polymorphism
1.What is the default access level for a class member with no modifier written?
2.What is the actual difference between private and protected?
3.What does
constructor(private name: string) {}do, compared to writingprivate name: string;above the constructor and assigning it manually?4.Does TypeScript's private keyword prevent access to that property at runtime, after compiling to JavaScript?
4 left to answer