Skip to main content
Engineering

Functional Programming Patterns in TypeScript

6 min read

In modern web development, managing state and minimizing side effects is crucial to building robust applications. Functional programming (FP) offers clean paradigms to address these issues. When paired with TypeScript's powerful static type checker, FP patterns help us write highly predictable and self-documenting code.

Vocabulary

Term Meaning
Immutability Data is never modified in place — updates return new copies
Pure function Same inputs always produce the same output with no side effects
Side effect Any change outside the function (DOM, network, global state)
Option / Maybe Type that models a value that may or may not exist — replaces null checks
Composition Building complex behavior from small, reusable functions

Steps

Step 1 — Enforce immutability with TypeScript

Avoiding shared mutable state is the cornerstone of functional programming. Instead of modifying objects directly, we create new instances with the modified values. In TypeScript, we can enforce this compile-time safety using the Readonly utility type or the readonly keyword.

typescript

Step 2 — Model optional values with an Option type

Null or undefined values are the source of many runtime crashes. We can implement a clean Option pattern (similar to Rust's Option or Haskell's Maybe) to model values that may or may not exist without using defensive if (val === null) checks everywhere.

typescript

Step 3 — Prefer pure functions in business logic

By moving away from class-based mutability and adopting pure functions, type assertions become absolute truths rather than temporal guesses. This results in codebases that are trivial to test, modular to refactor, and extremely resilient to scale.

Common pitfalls

  • Mutating nested objects even when the top level is spread — use Readonly deeply or Immer
  • Using Option everywhere when simple undefined checks suffice — match complexity to the problem
  • Mixing side effects inside "pure" helpers — keep I/O at the edges

Verify it works

Write a unit test that calls your pure function twice with the same input and asserts identical output. Confirm the original object is unchanged after an "update" function runs.

Takeaway

Immutability plus explicit optional types remove whole classes of bugs. Pure functions are easy to test — side effects belong at the boundaries.