04 - Classes
Basic Class
class Person {
name: string;
age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
greet(): string {
return `Hi, I'm ${this.name}`;
}
}
const person = new Person("Alice", 30);
person.greet(); // "Hi, I'm Alice"Access Modifiers
Control who can access properties:
class BankAccount {
public owner: string; // accessible everywhere (default)
private balance: number; // only inside this class
protected accountNumber: string; // inside this class + subclasses
constructor(owner: string, balance: number) {
this.owner = owner;
this.balance = balance;
this.accountNumber = Math.random().toString();
}
deposit(amount: number): void {
this.balance += amount; // ✅ private accessible inside the class
}
}
const account = new BankAccount("Alice", 100);
account.owner; // ✅ public
account.balance; // ❌ error — privateShorthand Constructor
TypeScript shortcut — declare and assign properties in one step:
// Instead of this:
class User {
name: string;
email: string;
id: number;
constructor(name: string, email: string, id: number) {
this.name = name;
this.email = email;
this.id = id;
}
}
// Write this:
class User {
constructor(
public name: string,
private email: string,
readonly id: number // readonly = can't be changed after creation
) {}
}Inheritance
A class can extend another class and inherit its properties/methods:
class Animal {
constructor(public name: string) {}
move(): void {
console.log(`${this.name} is moving`);
}
}
class Dog extends Animal {
bark(): void {
console.log("Woof!");
}
}
const dog = new Dog("Buddy");
dog.move(); // "Buddy is moving" — inherited from Animal
dog.bark(); // "Woof!" — own methodAbstract Classes
Can't be instantiated directly — only used as a base for other classes. Forces subclasses to implement certain methods:
abstract class Shape {
abstract area(): number; // subclasses MUST implement this
describe(): string { // shared method — subclasses inherit this
return `This shape has area ${this.area()}`;
}
}
class Circle extends Shape {
constructor(private radius: number) {
super();
}
area(): number { // required — abstract method
return Math.PI * this.radius ** 2;
}
}
// const shape = new Shape(); // ❌ error — can't instantiate abstract class
const circle = new Circle(5); // ✅
circle.area(); // 78.54
circle.describe(); // "This shape has area 78.54..."Implementing Interfaces
A class can implement an interface — guaranteeing it has certain properties/methods:
interface Printable {
print(): string;
}
interface Loggable {
log(): void;
}
class Report implements Printable, Loggable { // must implement both
constructor(private title: string) {}
print(): string {
return `Report: ${this.title}`;
}
log(): void {
console.log(this.print());
}
}Interface vs Class — When to Use Which
An interface only exists at compile time — it defines a shape but produces zero JavaScript. A class exists at runtime — it's real code with constructors and methods.
// Interface — erased after compilation, no JavaScript output
interface User {
name: string;
age: number;
}
const user: User = { name: 'Alice', age: 30 }; // just a plain object
// Class — real JavaScript, stays in the bundle
class UserAccount {
constructor(public name: string, public age: number) {}
greet() { return `Hi, I'm ${this.name}`; }
}
const account = new UserAccount('Alice', 30);
account.greet();Interfaces can define method signatures, but never implementations:
interface UserService {
getUser(id: number): Promise<User>;
deleteUser(id: number): Promise<void>;
}
// You don't need a class to satisfy it — any object with the right shape works:
const service: UserService = {
async getUser(id) { return fetch(`/api/users/${id}`).then(r => r.json()); },
async deleteUser(id) { await fetch(`/api/users/${id}`, { method: 'DELETE' }); }
};This is called structural typing — TypeScript doesn't care if you explicitly implements an interface. If the shape matches, it's valid.
| Interface | Class | |
|---|---|---|
| Runtime existence | ❌ erased | ✅ real JS |
| Method logic | ❌ signatures only | ✅ full implementations |
instanceof check |
❌ | ✅ |
| Bundle size impact | None | Adds code |
| Constructors | ❌ | ✅ |
implements required |
No (structural typing) | N/A |
Rule of thumb: Use interfaces by default for typing data shapes, API responses, and function parameters. Use classes only when you need runtime behavior — constructors, methods with logic, or instanceof checks.
Key Takeaways
public(default),private,protectedcontrol access- Shorthand constructor: put modifiers in constructor params
readonly= can't be changed after creationextends= inherit from a classabstract= base class that can't be instantiated, forces subclasses to implement methodsimplements= class must follow an interface's contract- Interface = compile-time only, defines shape, no runtime cost — use by default
- Class = runtime code, use when you need constructors, methods, or
instanceof - TypeScript uses structural typing — if the shape matches, it satisfies the interface (no
implementsneeded)