Classes and access modifiers
TypeScript classes are JavaScript classes with types added — plus a few genuinely useful features
JavaScript lacks: typed properties, access modifiers (private, public), parameter properties, and
more. Classes are less central to modern TypeScript than they once were (functions, objects, and unions
do a lot of the work), but they matter — especially in Angular and NestJS, which are built on them. This
lesson is the class, typed properly.
A typed class
class Member {
name: string;
age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
greet(): string {
return `Namaste, ${this.name}`;
}
}
const kavita = new Member("Kavita", 33);
kavita.greet(); // "Namaste, Kavita"
kavita.age.toFixed(); // fine — age is typed number
kavita.email; // error: Property 'email' does not exist on type 'Member'
Properties are declared with types (name: string), the constructor is typed, and methods have typed
parameters and returns. TypeScript checks every access — a property not on the class is an error. Under
strict mode, you must initialise every declared property (in the constructor or with a default), or
TypeScript flags it: an uninitialised name: string that could be undefined is a bug it catches.
Parameter properties — the boilerplate killer
The constructor above is repetitive: declare name, take name, assign this.name = name. TypeScript
has a shortcut — parameter properties — that declares and assigns in one place:
class Member {
constructor(
public name: string,
public age: number,
) {}
// that's it — name and age are declared, typed, AND assigned automatically
greet(): string {
return `Namaste, ${this.name}`;
}
}
Putting an access modifier (public, private, readonly) on a constructor parameter tells TypeScript
"make this a property and assign it". The whole name: string; constructor(name) { this.name = name }
dance collapses to constructor(public name: string). This is a genuinely nice feature — it removes real
boilerplate — and it is used heavily in NestJS (where dependencies are injected as private parameter
properties). Reach for it whenever a constructor just assigns its parameters to properties.
Access modifiers — public, private, protected
TypeScript adds access control that JavaScript historically lacked:
class BankAccount {
public readonly id: string; // visible everywhere, cannot be reassigned
private balance: number; // visible only inside this class
protected owner: string; // visible in this class and subclasses
constructor(id: string, owner: string) {
this.id = id;
this.owner = owner;
this.balance = 0;
}
deposit(amount: number): void {
this.balance += amount; // fine — inside the class
}
}
const acc = new BankAccount("A1", "Kavita");
acc.deposit(100); // fine — public method
acc.balance; // error: Property 'balance' is private
acc.id = "A2"; // error: id is readonly
public(the default) — accessible everywhere.private— accessible only within the class;acc.balancefrom outside is an error. Use it for internal state that outside code should not touch.protected— accessible within the class and its subclasses, but not from outside.readonly— combine with any of the above; the property can be set once (in the constructor) but never reassigned.
Encapsulation — hiding internal state behind private and exposing only what is meant to be used — is
the point: it lets you change the internals without breaking callers, and prevents outside code from
putting the object in a bad state. Make properties private by default and expose only what genuinely
needs to be public.
One caveat: TypeScript's private is a compile-time check (erased at runtime). For true runtime
privacy, JavaScript has native #private fields:
class Account {
#balance = 0; // truly private at runtime — inaccessible outside, even via JS
deposit(n: number) { this.#balance += n; }
}
#balance is genuinely inaccessible outside the class at runtime, not just in TypeScript's view. Prefer
#private when runtime privacy matters (a library's internals); TypeScript's private is fine for
ordinary application code where compile-time checking is enough.
Getters, setters, and static members
Classes support computed accessors and class-level members:
class Circle {
constructor(private radius: number) {}
get area(): number { // a getter — accessed like a property
return Math.PI * this.radius ** 2;
}
set diameter(value: number) { // a setter — assigned like a property
this.radius = value / 2;
}
static fromDiameter(d: number): Circle { // a static method — called on the class
return new Circle(d / 2);
}
}
const c = new Circle(10);
c.area; // 314.159... — no parentheses; it is a getter
c.diameter = 20; // calls the setter -> radius becomes 10
const c2 = Circle.fromDiameter(20); // static factory method
A getter (get area()) is accessed like a property but computes its value — right for a derived
value. A setter (set diameter()) runs code on assignment. Static members (static fromDiameter) belong to the class itself, not an instance — called as Circle.fromDiameter(...), and
the idiomatic place for factory methods (like the factory pattern the design-patterns module covers).
When to use a class — and when not
Be honest, because beginners from Java over-use classes in TypeScript:
- Use a class when you have data and behaviour bundled together with genuine internal state and
encapsulation (a
BankAccount, aRepository), when a framework requires it (Angular components, NestJS services), or when you need instances with identity. - Do not use a class for plain data — a
type/interfaceand a plain object is lighter and more idiomatic (type User = { name: string }, not aUserclass with only fields). And do not use a class with a single method and no state — that is a function wearing a costume (the design-patterns lesson).
Much of what classes do in Java, TypeScript does with types, functions, and discriminated unions. Reach for a class when you genuinely need bundled state and behaviour or a framework demands it — not by reflex.
Check your work
How class properties are typed. Declared with types (name: string); strict mode requires every
property be initialised.
What parameter properties do. An access modifier on a constructor parameter declares and assigns it in one place — killing the declare/assign boilerplate.
The three access modifiers. public (default, everywhere), private (this class only), protected
(this class and subclasses); readonly combines with any.
What encapsulation buys. Hiding internal state behind private lets you change internals without
breaking callers and prevents bad states.
private versus #private. TypeScript's private is compile-time (erased); #field is true
runtime privacy — prefer # when runtime privacy matters.
Getters, setters, static. A getter computes a property-like value; a setter runs code on assignment; static members belong to the class (factory methods).
When to use a class, and when not. For bundled state+behaviour or framework requirements; not for plain data (use a type) or a single stateless method (use a function).
Practice
- Write a
Memberclass with typed properties, a constructor, and a method. Access a non-existent property and read the error. - Rewrite the constructor using parameter properties and confirm the behaviour is identical.
- Make a property
privateand try to access it from outside. Read the error. Make onereadonlyand try to reassign it. - Convert a
privatefield to a#privatefield and note it is inaccessible even to raw JavaScript. - Add a getter and a setter and use them like properties.
- Add a
staticfactory method and call it on the class. - For three things you might model as a class, decide whether a class, a
type+object, or a function is the right tool, and justify each.
Official documentation
- TypeScript — Classes — Properties, constructors, methods, modifiers.
- TypeScript — Parameter properties — The boilerplate killer.
- TypeScript — Member visibility —
public/private/protectedand#private.
Next: abstract classes and implementing interfaces.
Stuck on this lesson?
Being stuck is part of it — but being stuck alone for three days is not. Our internship programme pairs this curriculum with code review and one-to-one help from working developers, and it is free.
About the internship