Functional Programming in TypeScript: Fundamentals and Practice

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

Functional Programming in TypeScript: from Theory to Practice



Functional Programming (FP) is a paradigm in which computation is treated as the evaluation of mathematical functions and avoids changing state and mutable data. TypeScript, being a superset of JavaScript, is well-suited for applying FP principles due to its strict typing and support for higher-order functions. In this article, we will break down the key concepts of FP in TypeScript and show how to apply them in practice.



1. Pure Functions and Immutability



A pure function is a function that:

  • Always returns the same result for the same arguments (deterministic).
  • Has no side effects (does not modify external variables, does not output data, does not make requests).


Example of a pure function:

// Pure function: sums two numbersconst add = (a: number, b: number): number => a + b;

// Always returns 5console.log(add(2, 3)); // 5console.log(add(2, 3)); // 5


Immutability is the principle where data is not changed after creation. Instead of modifying an existing object, a new one is created.



// Immutable array updateconst numbers: number[] = [1, 2, 3];

// Bad (mutation):numbers.push(4);

// Good (immutable):const newNumbers = [...numbers, 4]; // [1,2,3,4]

// Immutable object updateinterface User { name: string; age: number;}

const user: User = { name: 'Alice', age: 30 };const updatedUser: User = { ...user, age: 31 }; // new object


2. Function Composition and Currying



Function composition is combining two or more functions into a new one. The result of the first function is passed as an argument to the second.



// Composition of two functionsconst compose = <T, U, V>(f: (x: U) => V, g: (x: T) => U) =>  (x: T): V => f(g(x));

const double = (x: number): number => x * 2;const increment = (x: number): number => x + 1;

const doubleThenIncrement = compose(increment, double);console.log(doubleThenIncrement(3)); // (3*2)+1 = 7


Currying is transforming a function with multiple arguments into a sequence of functions with a single argument.



// Regular functionconst multiply = (a: number, b: number): number => a * b;

// Curried versionconst curriedMultiply = (a: number) => (b: number): number => a * b;

const multiplyBy2 = curriedMultiply(2);console.log(multiplyBy2(5)); // 10console.log(multiplyBy2(7)); // 14


Currying simplifies partial application and function reuse.



3. Functors and Monads: Maybe and Either



A functor is a container that implements the map method, allowing a function to be applied to the value inside the container. In TypeScript, an array is an example of a functor:



const numbers = [1, 2, 3];const doubled = numbers.map(x => x * 2); // [2, 4, 6]


A monad is a functor that additionally implements the flatMap (or bind) method and of (or return). Monads help handle side effects, null values, and errors without exceptions.



Let's implement a simple Maybe monad for safe handling of null/undefined:



// Maybe monadclass Maybe<T> {  private constructor(private value: T | null) {}

static of<T>(value: T): Maybe<T> { return new Maybe(value); }

static none<T>(): Maybe<T> { return new Maybe<T>(null); }

map<U>(fn: (value: T) => U): Maybe<U> { if (this.value === null || this.value === undefined) { return Maybe.none(); } return M

Blogs

Book Recommendations