013 — Access Modifiers (public, private, readonly, shorthand)
Full companion narrative for TypeScript Tutorial — Classes: public, private, readonly: who may read or change each Invoice property, and the constructor parameter-property shorthand
Video: 013 · TypeScript Tutorial - Classes: public, private, readonly · watch · ~6:05 · Net Ninja TypeScript tutorial series, module 05 (objects and interfaces)
Source: this is a written companion to the video. Part of the Favorite TypeScript Videos curated path, module 05 (objects and interfaces). The second classes lesson: the
Invoiceclass from 012 already works — now the question is who is allowed to read or change each property, and where.
the Invoice class with everything open
The lesson opens with the Invoice class as built previously: three properties (client: string, details: string, amount: number), a constructor that assigns them, and a format() method returning a sentence. Two instances (invOne for mario, invTwo for luigi) sit in an invoices array, and a forEach loop logs each invoice’s client, details, amount, and format() result to the console.
class Invoice {
client: string;
details: string;
amount: number;
constructor(client: string, details: string, amount: number) {
this.client = client;
this.details = details;
this.amount = amount;
}
format(): string {
return `${this.client} owes £${this.amount} for ${this.details}`;
}
}
const invOne = new Invoice("mario", "work on the mario website", 250);
const invTwo = new Invoice("luigi", "work on the luigi website", 300);
const invoices: Invoice[] = [invOne, invTwo];
invoices.forEach((inv) => {
console.log(inv.client, inv.details, inv.amount, inv.format());
});
// console: mario / work on the mario website / 250 / "mario owes £250 for ..."
// console: luigi / work on the luigi website / 300 / "luigi owes £300 for ..."


The key observation: every property can be both read and reassigned from outside the class after construction. That is the default, and the video makes it explicit before changing anything.
public, the default
Every property without a modifier is public: readable and writable both inside the class and on instances outside it. Writing public in front changes nothing — it just states the default out loud.
class Invoice {
public client: string; // identical to `client: string;` — public by default
// ...
}
invOne.client = "yoshi"; // allowed: public means anyone can read or change it
console.log(invOne.client); // allowed
Teach “no modifier = public” as the baseline, then present private and readonly as restrictions on top of it.
private fields, inside-only access
Marking a property private locks it to the class body. Code inside the class (e.g. this.details in format()) can still read and write it; code outside, on an instance (inv.details), cannot — the compiler rejects it.
The video demonstrates this by making details private: the console.log(inv.details) line in the loop immediately errors, so the presenter deletes that direct access. The format() method keeps working because it touches this.details from inside the class, and the details still appear in the console via the formatted sentence.
class Invoice {
client: string;
private details: string; // inside-only from here on
amount: number;
// constructor + format() as before
}
invoices.forEach((inv) => {
// console.log(inv.details); // ERROR: Property 'details' is private
console.log(inv.client, inv.amount, inv.format()); // fine — format() reads details internally
});


private is about encapsulation — hide the raw field, expose it only through a method you control.

readonly fields, readable but frozen
readonly splits the difference: the property can be read both inside and outside the class, but cannot be reassigned anywhere — not on the instance, not even with this. inside the class body. (The initial assignment in the constructor is the one exception; after that it is frozen.)
The video demonstrates both failure modes: inv.client = "..." outside the class errors, and this.client = "..." inside the class errors too.
class Invoice {
readonly client: string;
private details: string;
public amount: number;
// ...
}
// invOne.client = "yoshi"; // ERROR: Cannot assign to 'client' because it is a read-only property.
// this.client = "yoshi"; // ERROR inside the class as well — same rule.
console.log(invOne.client); // fine — readonly is still readable everywhere.

Summary table the video builds:
| modifier | read outside | change outside | read/write inside |
|---|---|---|---|
| public (default) | yes | yes | yes |
| private | no | no | yes |
| readonly | yes | no | read-only (no reassignment, even inside) |
modifiers on parameters replace the boilerplate
The closing lesson: when every property uses an access modifier, the explicit field declarations plus manual this.x = x assignments collapse into the constructor signature. Prefixing a constructor parameter with public, private, or readonly tells TypeScript to create a property of that name and auto-assign the passed value.
Requirements stated in the video: the shorthand only works with a modifier present — bare parameters without one do not become properties.
// longhand (commented out in the video) vs shorthand (kept):
class Invoice {
// readonly client: string;
// private details: string;
// public amount: number;
// constructor(client: string, details: string, amount: number) {
// this.client = client;
// this.details = details;
// this.amount = amount;
// }
constructor(
readonly client: string,
private details: string,
public amount: number,
) {}
format(): string {
return `${this.client} owes £${this.amount} for ${this.details}`;
}
}
// behavior unchanged: mario 250 / luigi 300 still log exactly as before.

This shorthand is the idiom learners will meet in real codebases — the preferred form, with the longhand shown once so the auto-assignment is not magic.
The outro splits the class into its own file — 014 — Modules.
Prev: 012 — Classes · Next: 014 — Modules
Transcript notes: loop variable inv (not “in”), forEach, property as a named value on the class, instance as one new Invoice(...) object, “outside the class” meaning instance code vs this. body code. Each change is demoed by breaking it on purpose, reading the compiler error, and reverting. Modules and interfaces follow next.