SOLID Principles in TypeScript: Theory and Practice with Examples

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

Introduction: Why SOLID is Important in TypeScript



SOLID is an acronym for five fundamental principles of object-oriented design formulated by Robert Martin (Uncle Bob). In the context of TypeScript, these principles gain particular value thanks to the strict type system and support for modern OOP paradigms. Applying SOLID makes code more flexible, testable, and maintainable, which is critically important for scalable projects.



In this article, we will break down each SOLID principle using practical TypeScript examples, show antipatterns and their correct implementations. You will learn how to write code that doesn't break when adding new functionality.



1. Single Responsibility Principle (SRP)



Theory

Each class should have only one reason to change. This means that a class should be responsible for only one part of the program's functionality. If a class performs multiple tasks, it is harder to maintain and test.



Antipattern (SRP violation)

class UserService {    saveUser(user: User): void {        // Saving to DB        console.log('User saved');    }

sendEmail(user: User, message: string): void { // Sending email console.log(`Email sent to ${user.email}`); }

generateReport(user: User): string { // Generating report return `Report for ${user.name}`; }}


Correct implementation (SRP)

class UserRepository {    save(user: User): void {        // Only saving    }}

class EmailService { send(user: User, message: string): void { // Only sending email }}

class ReportGenerator { generate(user: User): string { // Only generating reports }}


2. Open-Closed Principle (OCP)



Theory

Classes should be open for extension but closed for modification. New functionality should be added through inheritance or composition, not by changing existing code.



Antipattern (OCP violation)

class PaymentProcessor {    processPayment(type: string, amount: number): void {        if (type === 'card') {            // Card processing        } else if (type === 'paypal') {            // PayPal processing        } else if (type === 'crypto') {            // Cryptocurrency processing        }        // When adding a new type — you need to change the class!    }}


Correct implementation (OCP)

interface PaymentMethod {    process(amount: number): void;}

class CardPayment implements PaymentMethod { process(amount: number): void { console.log(`Processing card payment: $${amount}`); }}

class PayPalPayment implements PaymentMethod { process(amount: number): void { console.log(`Processing PayPal payment: $${amount}`); }}

class PaymentProcessor { constructor(private paymentMethod: PaymentMethod) {}

process(amount: number): void { this.paymentMethod.process(amount); }}


3. Liskov Substitution Principle (LSP)



Theory

Objects of a base class should be replaceable with objects of its subclasses without breaking the program. Subclasses should not alter the expected behavior of the base class.



Antipattern (LSP violation)

class Rectangle {    constructor(public width: number, public height: number) {}

setWidth(width: number): void { this.width = width; }

setHeight(height: number): void { this.height = height; }

getArea(): number { return this.width * this.height; }}

class Square extends Rectangle {

Blogs

Book Recommendations