Understanding SOLID, DRY, and KISS in Software Development (with TypeScript Examples)


Writing clean, maintainable, and scalable code is essential for software developers. Principles like SOLID, DRY, and KISS help achieve this goal. Here’s a practical look with TypeScript examples.

1. SOLID Principles

SOLID is an acronym for five key object-oriented design principles introduced by Robert C. Martin.

S - Single Responsibility Principle (SRP)

A class should have only one reason to change.

// Bad: One class handles both logging and user management
class UserManager {
  addUser(user: User) { /* ... */ }
  removeUser(userId: number) { /* ... */ }
  log(message: string) { console.log(message); }
}


// Good: Separate responsibilities
class Logger {
  log(message: string) { console.log(message); }
}


class UserManager {
  constructor(private logger: Logger) {}
  addUser(user: User) { this.logger.log('User added'); }
}

O - Open/Closed Principle (OCP)

Software entities should be open for extension but closed for modification.

interface Discount {
  calculate(price: number): number;
}


class NoDiscount implements Discount {
  calculate(price: number) { return price; }
}


class SummerDiscount implements Discount {
  calculate(price: number) { return price * 0.9; }
}

L - Liskov Substitution Principle (LSP)

Subtypes must be substitutable for their base types.

class Bird { fly() {} }
class Duck extends Bird { fly() { console.log('Duck flying'); } }
class Ostrich extends Bird {
  fly() { throw new Error("Ostriches can't fly"); } // violates LSP
}

I - Interface Segregation Principle (ISP)

Use small, specific interfaces.

interface Printer { print(): void; }
interface Scanner { scan(): void; }


class MultiFunctionPrinter implements Printer, Scanner {
  print() { console.log('Printing'); }
  scan() { console.log('Scanning'); }
}

D - Dependency Inversion Principle (DIP)

Depend on abstractions, not on concrete implementations.

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


class PayPalProcessor implements PaymentProcessor {
  process(amount: number) { console.log(`PayPal: ${amount}`); }
}


class Checkout {
  constructor(private processor: PaymentProcessor) {}
  pay(amount: number) { this.processor.process(amount); }
}

2. DRY - Don’t Repeat Yourself

Avoid duplicating logic; extract reusable functions.

// Bad
const calculatePriceWithTax = (price: number) => price * 1.2;
const calculateTotalOrderPrice = (price: number) => price * 1.2;

// Good
const applyTax = (price: number, taxRate: number = 0.2) => price * (1 + taxRate);

3. KISS - Keep It Simple, Stupid

Favor simple, easy-to-understand solutions over complex ones.

// Complex way using nca-style arrow (explicit return)
const getEvenNumbers = (nums: number[]): number[] =>
  nums.filter(n => { if (n % 2 === 0) { return true; } return false; });

// Simple way using concise arrow
const getEvenNumbersSimple = (nums: number[]): number[] =>
  nums.filter(n => n % 2 === 0);

Conclusion

By applying SOLID, DRY, and KISS, TypeScript developers can write code that is clean, reusable, and maintainable. SOLID improves object-oriented design, DRY reduces repetition, and KISS keeps solutions simple and efficient.

References

  • Robert C. Martin, Clean Code.
  • Martin Fowler, Refactoring: Improving the Design of Existing Code.
  • TypeScript Official Documentation.
Bartłomiej Nowak

Bartłomiej Nowak

Programmer

Programmer focused on performance, simplicity, and good architecture. I enjoy working with modern JavaScript, TypeScript, and backend logic — building tools that scale and make sense.

Recent Posts