Functional Programming in TypeScript: From Theory to Practice
Functional Programming (FP) is a paradigm based on computation through the application of functions and avoiding state mutation. TypeScript, being a strictly typed superset of JavaScript, provides powerful tools for implementing FP. In this article, we will break down the key concepts of functional programming in TypeScript with practical code examples.
1. Pure Functions and Immutability
A pure function is a function that:
- Always returns the same result for the same arguments (determinism).
- Has no side effects (does not modify external variables, does not perform I/O operations).
// Pure functionconst add = (a: number, b: number): number => a + b;
// Impure function (side effect — modifying external state)let counter = 0;const increment = (): number => ++counter;Immutability means that data is not changed, but new copies are created. In TypeScript, you can use the spread operator and non-mutating methods:
interface User { name: string; age: number;}
// Mutation (bad)const updateAgeMutable = (user: User, newAge: number): void => { user.age = newAge;};
// Immutable approach (good)const updateAgeImmutable = (user: User, newAge: number): User => ({ ...user, age: newAge,});Immutability simplifies debugging and testing, as data is always predictable.
2. Higher-Order Functions and Closures
Higher-order functions are functions that take other functions as arguments or return them. An example is the Array.map method:
const numbers: number[] = [1, 2, 3, 4];const doubled: number[] = numbers.map((x) => x * 2);console.log(doubled); // [2, 4, 6, 8]A closure is a function that "remembers" variables from its outer scope even after the outer function has finished executing:
const createMultiplier = (factor: number): ((x: number) => number) => { return (x: number) => x * factor;};
const double = createMultiplier(2);const triple = createMultiplier(3);
console.log(double(5)); // 10console.log(triple(5)); // 15Closures are widely used to create functions with partial application of arguments (currying).
3. Function Composition and Currying
Function composition is combining several functions into one, where the output of one function is the input for the next. In TypeScript, you can write a utility for composition:
const compose = <T, R>(...fns: Array<(arg: T) => T>) => (arg: T): T => fns.reduceRight((acc, fn) => fn(acc), arg);
// Example: string transformationconst toUpperCase = (s: string): string => s.toUpperCase();const exclaim = (s: string): string => `${s}!`;const shout = compose(toUpperCase, exclaim);
console.log(shout("hello")); // "HELLO!"Currying is transforming a function with multiple arguments into a sequence of functions with a single argument:
// Regular functionconst add = (a: number, b: number): number => a + b;
// Curried versionconst curriedAdd = (a: number) => (b: number): number => a + b;
const add5 = curriedAdd(5);console.log(add5(3)); // 8Currying simplifies creating specialized functions and improves code reusability.
4. Monads and Working with Option/Maybe
A monad is a functional programming pattern that encapsulates a value and provides an interface for transforming it. One of the simplest monads is Maybe (or Option), which safely handles n