TypeScript classes build on JavaScript classes and add the pieces object-oriented developers expect: access modifiers, abstract classes, interface implementation, and concise constructor syntax. Even if you write mostly functional code, classes appear throughout popular frameworks and libraries, so understanding TypeScript's class features is essential.
Fields are declared with types; the constructor initializes them:
class Account {
owner: string;
balance: number;
constructor(owner: string, openingBalance: number) {
this.owner = owner;
this.balance = openingBalance;
}
deposit(amount: number): void {
this.balance += amount;
}
}
const acct = new Account("Ada", 100);
acct.deposit(50);With strictPropertyInitialization enabled (part of strict mode), every declared field must be initialized in the constructor or given a default — another whole class of bugs eliminated.
TypeScript adds visibility keywords checked at compile time:
class BankAccount {
private balance = 0; // only this class
protected owner: string; // this class and subclasses
public readonly id: string; // anyone can read, nobody can reassign
constructor(owner: string, id: string) {
this.owner = owner;
this.id = id;
}
getBalance(): number {
return this.balance; // internal access is fine
}
}
const b = new BankAccount("Grace", "acc-1");
b.balance; // Error: balance is privateNote that private is erased at compile time — at runtime the property still exists. For true runtime privacy, JavaScript's native #field syntax works in TypeScript too and enforces privacy in the emitted code.
Declaring and assigning fields in one step is so common that TypeScript has a shorthand — put a modifier on a constructor parameter:
class User {
constructor(
public readonly id: number,
private passwordHash: string,
public name: string = "Anonymous",
) {}
}
const u = new User(1, "x9f2...");
console.log(u.name); // "Anonymous"Each modified parameter automatically becomes a class field with that visibility.
class Temperature {
constructor(private celsius: number) {}
get fahrenheit(): number {
return this.celsius * 1.8 + 32;
}
set fahrenheit(value: number) {
this.celsius = (value - 32) / 1.8;
}
}Inheritance uses extends, and super(...) must run before touching this. Mark methods designed for overriding clearly, and enable the noImplicitOverride option so subclasses must write the override keyword — the compiler then catches renamed base methods.
An abstract class cannot be instantiated; it defines a partial implementation plus required methods:
abstract class Shape {
abstract area(): number;
describe(): string {
return "Area: " + this.area();
}
}
class Circle extends Shape {
constructor(private radius: number) { super(); }
area(): number { return Math.PI * this.radius ** 2; }
}Separately, a class can promise to match an interface with implements:
interface Serializable {
toJSON(): string;
}
class Config implements Serializable {
toJSON(): string { return "{}"; }
}implements only checks the shape — it adds no runtime behavior. Because TypeScript is structural, any object with a toJSON(): string method satisfies Serializable, class or not.
public, private, and protected are compile-time checks; #fields give runtime privacy.implements verifies a class matches an interface.override (with noImplicitOverride) to make inheritance refactoring-safe.Next lesson: Generics — writing components that work over many types without losing safety.