Last 30 Days
No notifications
TypeScript classes are JavaScript classes plus a type system on top: public / private / protected visibility, readonly fields, abstract classes, parameter properties, and the difference between implements and extends. You'll use them whenever you build a service, a value object, or a React class component (rare these days).
class User {
name: string;
age: number; constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
greet(): string {
return Hi ${this.name};
}
}
const u = new User("Ada", 36);
console.log(u.greet());
Add a visibility modifier to a constructor parameter and TS auto-creates the field + assigns it.
class User {
constructor(public name: string, public age: number) {}
greet() { return Hi ${this.name}; }
}Five lines instead of nine. Use it everywhere.
| Modifier | Visible from |
public (default) | Anywhere |
protected | The class and its subclasses |
private | Only the class itself |
#field (JS) | Truly private at runtime |
class Account {
protected balance = 0;
private secret = "hunter2"; deposit(n: number) { this.balance += n; }
}
class Savings extends Account {
bonus() { this.balance *= 1.05; } // ✅ protected
// leak() { return this.secret; } // ❌ private
}
private is enforced by TypeScript only — JS code can still poke the field. For real runtime privacy use #field:
class Wallet {
#pin: string;
constructor(pin: string) { this.#pin = pin; }
// No way to read #pin from outside, even at runtime
}readonly fieldsclass Point {
constructor(public readonly x: number, public readonly y: number) {}
}const p = new Point(1, 2);
// p.x = 5; // ❌
Belong to the class, not instances.
class Logger {
static instances = 0;
constructor() { Logger.instances++; }
}
new Logger(); new Logger();
console.log(Logger.instances); // 2extends + superclass Animal {
constructor(public name: string) {}
speak() { return ${this.name} makes a sound; }
}class Dog extends Animal {
constructor(name: string, public breed: string) {
super(name);
}
speak() { return ${this.name} barks!; } // override
}
const d = new Dog("Rex", "Lab");
console.log(d.speak()); // "Rex barks!"
Use super.method() to call the parent's version when overriding:
class Loud extends Dog {
speak() { return super.speak().toUpperCase(); }
}abstract classes can't be instantiated — they exist to be extended.
abstract class Shape {
abstract area(): number; // subclass MUST implement
describe() { return Area: ${this.area()}; }
}class Circle extends Shape {
constructor(public r: number) { super(); }
area() { return Math.PI * this.r ** 2; }
}
// new Shape(); // ❌
new Circle(5); // ✅
Use abstract classes when you want shared implementation + a contract. Use interfaces when you only want the contract.
implements — promise to satisfy a shapeinterface Repository<T> {
find(id: number): Promise<T | null>;
save(item: T): Promise<void>;
}class UserRepo implements Repository<User> {
async find(id: number) { return null; }
async save(u: User) {}
}
You can implement *multiple* interfaces; you can only extends one class.
class Temperature {
#celsius = 0; get fahrenheit() { return this.#celsius * 9 / 5 + 32; }
set fahrenheit(f: number) { this.#celsius = (f - 32) * 5 / 9; }
}
const t = new Temperature();
t.fahrenheit = 212;
console.log(t.fahrenheit); // 212
class Stack<T> {
private items: T[] = [];
push(x: T) { this.items.push(x); }
pop(): T | undefined { return this.items.pop(); }
}| Use a class | Use a plain object |
Identity matters (instanceof) | Pure data |
| You need methods that operate on shared state | Most React props / API responses |
| You want inheritance or a contract | Functional code |
| You're building a service / repository | Stateless utilities |
In modern frontend code, plain objects + standalone functions usually win — classes shine in services, ORMs, parsers, and games.
thisclass Counter {
count = 0;
inc() { this.count++; }
}
const c = new Counter();
const fn = c.inc;
// fn(); // ❌ Cannot read 'count' of undefinedFix by binding (fn = c.inc.bind(c)) or using an arrow-function field:
class Counter {
count = 0;
inc = () => { this.count++; }; // bound to instance forever
}