Implementing SOLID Principles in TypeScript

Lakin Mohapatra
4 min readJan 28, 2025

--

SOLID principles are a set of design principles that help developers create clean, maintainable, and scalable software. These principles are especially important in object-oriented programming and can be effectively applied in TypeScript.

In this article, we will explore each of the SOLID principles and demonstrate how to implement them in TypeScript with practical examples.

1. Single-Responsibility Principle (SRP)

A class should have only one reason to change, meaning it should have only one responsibility.

Bad Implementation

class User {
constructor(private name: string, private email: string) {}

saveToDatabase(): void {
console.log(`Saving user ${this.name} to database...`);
}

sendEmail(subject: string, body: string): void {
console.log(`Sending email to ${this.email}: ${subject}`);
}
}

Problem: User class handles both user data management and email sending, violating SRP.

Good Implementation

Split responsibilities into separate classes (UserRepository for database operations and EmailService for email sending).

class User {
constructor(private name: string, private email: string) {}
}

class UserRepository {
saveToDatabase(user: User): void {
console.log(`Saving user ${user.name} to database...`);
}
}

class EmailService {
sendEmail(user: User, subject: string, body: string): void {
console.log(`Sending email to ${user.email}: ${subject}`);
}
}

2. Open-Closed Principle (OCP)

Software entities (classes, modules, functions) should be open for extension but closed for modification.

Bad Implementation

class Discount {
giveDiscount(customerType: string): number {
if (customerType === "regular") {
return 10;
} else if (customerType === "premium") {
return 20;
}
return 0;
}
}

Problem: Adding a new customer type requires modifying the Discount class.

Good Implementation

Use interfaces and inheritance to extend functionality without modifying existing code.

interface Customer {
getDiscount(): number;
}

class RegularCustomer implements Customer {
getDiscount(): number {
return 10;
}
}

class PremiumCustomer implements Customer {
getDiscount(): number {
return 20;
}
}

class Discount {
giveDiscount(customer: Customer): number {
return customer.getDiscount();
}
}

3. Liskov Substitution Principle (LSP)

Objects of a superclass should be replaceable with objects of a subclass without affecting the correctness of the program.

Bad Implementation

class Rectangle {
constructor(public width: number, public height: number) {}
setWidth(width: number): void {
this.width = width;
}
setHeight(height: number): void {
this.height = height;
}
area(): number {
return this.width * this.height;
}
}

class Square extends Rectangle {
setWidth(width: number): void {
this.width = width;
this.height = width; // Violates LSP
}
setHeight(height: number): void {
this.height = height;
this.width = height; // Violates LSP
}
}

Problem: Square changes the behavior of Rectangle, violating LSP.

Good Implementation

Use a common interface (Shape) to ensure substitutability.

interface Shape {
area(): number;
}

class Rectangle implements Shape {
constructor(public width: number, public height: number) {}
area(): number {
return this.width * this.height;
}
}

class Square implements Shape {
constructor(public side: number) {}
area(): number {
return this.side * this.side;
}
}

4. Interface Segregation Principle (ISP)

Clients should not be forced to depend on interfaces they do not use.

Bad Implementation

interface Worker {
work(): void;
eat(): void;
}

class Engineer implements Worker {
work(): void {
console.log("Engineering work...");
}
eat(): void {
console.log("Eating...");
}
}

class Robot implements Worker {
work(): void {
console.log("Building...");
}
eat(): void {
throw new Error("Robots don't eat!");
}
}

Problem: Robot is forced to implement the eat method, which it doesn’t need.

Good Implementation

Split the interface into smaller, more specific interfaces.

interface Workable {
work(): void;
}

interface Eatable {
eat(): void;
}

class Engineer implements Workable, Eatable {
work(): void {
console.log("Engineering work...");
}
eat(): void {
console.log("Eating...");
}
}

class Robot implements Workable {
work(): void {
console.log("Building...");
}
}

5. Dependency Inversion Principle (DIP)

High-level modules should not depend on low-level modules. Both should depend on abstractions.

Bad Implementation

class MySQLDatabase {
save(data: string): void {
console.log(`Saving ${data} to MySQL database...`);
}
}

class App {
private database = new MySQLDatabase();
saveData(data: string): void {
this.database.save(data);
}
}

Problem: App is tightly coupled to MySQLDatabase.

Good Implementation

Use dependency injection and depend on abstractions (interfaces) rather than concrete implementations.

interface Database {
save(data: string): void;
}

class MySQLDatabase implements Database {
save(data: string): void {
console.log(`Saving ${data} to MySQL database...`);
}
}

class MongoDBDatabase implements Database {
save(data: string): void {
console.log(`Saving ${data} to MongoDB database...`);
}
}

class App {
constructor(private database: Database) {}
saveData(data: string): void {
this.database.save(data);
}
}

const mySQLApp = new App(new MySQLDatabase());
mySQLApp.saveData("User Data");

const mongoDBApp = new App(new MongoDBDatabase());
mongoDBApp.saveData("User Data");

Conclusion

By following the SOLID principles, you can create TypeScript applications that are:

  1. Modular (Single-Responsibility Principle).
  2. Extensible (Open-Closed Principle).
  3. Reliable (Liskov Substitution Principle).
  4. Flexible (Interface Segregation Principle).
  5. Decoupled (Dependency Inversion Principle).

These principles help you write clean, maintainable, and scalable code, making your applications easier to understand, extend, and debug.

Start applying SOLID principles in your TypeScript projects today!

--

--

Lakin Mohapatra
Lakin Mohapatra

Written by Lakin Mohapatra

Software Engineer | Hungry coder | Proud Indian | Cyber Security Researcher | Blogger | Architect (web2 + web 3)

No responses yet