Two Sides of the Same Coin: Functional and OOP Approaches in TypeScript
\n\nThe world of TypeScript is an amazing place where best practices from different worlds blend together. Have you ever wondered why some developers write code using classes and inheritance, while others use pure functions and composition? And most importantly: which approach will bring more benefit to your project?
\n\nIn this article, we won't argue which is better. We will break down functional programming versus object-oriented programming in TypeScript with real-world examples. You will learn when to use classes and when to use higher-order functions, and how to find the sweet spot in modern development.
\n\nWhat is Functional Programming in TypeScript?
\n\nFunctional programming (FP) is a paradigm where the main building blocks are pure functions. They do not modify external state and always return the same result for the same input. TypeScript supports FP excellently thanks to its strong typing and capabilities for working with functions as data.
\n\nCore Principles of FP in TypeScript
\n- \n
- Immutability — data is not changed, but created anew \n
- Pure functions — no side effects \n
- Composition — building complex behavior from simple functions \n
- Higher-order functions — functions that take or return other functions \n
// Example of pure functions and immutability in TypeScript\ntype User = {\n readonly id: number;\n readonly name: string;\n readonly age: number;\n};\n\nconst addYear = (user: User): User => ({\n ...user,\n age: user.age + 1\n});\n\nconst users: User[] = [\n { id: 1, name: 'Alice', age: 25 },\n { id: 2, name: 'Bob', age: 30 }\n];\n\n// No mutations — creating a new array\nconst updatedUsers = users.map(addYear);\nconsole.log(users[0].age); // 25 (original unchanged)\nconsole.log(updatedUsers[0].age); // 26\n\n\nNotice readonly — this is a powerful TypeScript feature that helps enforce immutability at the type level.
What is Object-Oriented Programming in TypeScript?
\n\nOOP is built around objects that combine data and behavior. TypeScript adds classical inheritance, access modifiers, and interfaces to JavaScript, making OOP even more expressive.
\n\nCore Principles of OOP in TypeScript
\n- \n
- Encapsulation — hiding internal state via
privateandprotected\n - Inheritance — creating class hierarchies \n
- Polymorphism — a single interface for different implementations \n
- Abstraction — working through interfaces, not concrete classes \n
// Example of OOP approach in TypeScript\nabstract class Animal {\n constructor(protected name: string) {}\n\n abstract makeSound(): string;\n\n public introduce(): string {\n return `I am ${this.name}, and I say ${this.makeSound()}`;\n }\n}\n\nclass Dog extends Animal {\n constructor(name: string) {\n super(name);\n }\n\n public makeSound(): string {\n return 'Woof!';\n }\n}\n\nclass Cat extends Animal {\n constructor(name: string) {\n super(name);\n }\n\n public makeSound(): string {\n return 'Meow!';\n }\n}\n\nconst animals: Animal[] = [new Dog('Rex'), new Cat('Whiskers')];\nanimals.forEach(a => console.log(a.introduce()));\n// I am Rex, and I say Woof!\n// I am Whiskers, and I say Meow!\n\n\nHere, abstract and protected are TypeScript features not present in plain JavaScript. They allow building a strict architecture directly at the type level.
Comparing Approaches: When to Use What?
\n\nLet's break down a few