
TypeScript Beyond Basics
} -->
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.
SOLID is an acronym for five key object-oriented design principles introduced by Robert C. Martin.
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'); }
}
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; }
}
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
}
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'); }
}
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); }
}
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);
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);
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.
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.