Pattern matching in TypeScript with ts-pattern

27 november 2025

What is pattern matching?

Pattern matching is a programming technique where you compare a value or structure against a predefined pattern and, depending on which match occurs, perform different actions. It can be seen as a more advanced and flexible form of conditional expressions like if-else or switch-case. Instead of only comparing values, pattern matching can check both the types and structures of data and then execute different operations based on the result.

How does pattern matching work?

Pattern matching attempts to match an object or a value against a set of predefined patterns. Each pattern describes a certain structure or value. If the current object or value matches a specific pattern, the corresponding code block for that pattern is executed. This can include:

  • Type matching: Matching based on type (e.g., whether an object is of type Circle or Square).
  • Value matching: Checking if a value matches exactly.
  • Structural matching: Comparing parts of an object or data structure, for example, whether a certain key in an object has a specific value.
  • Conditional matching: Using additional conditions to refine the match, such as checking that a value is greater than zero.

Advantages of pattern matching

  • Readability: Code becomes easier to understand when complex logic can be broken down into simple patterns.
  • Safety: By specifying all possible patterns, you reduce the risk of errors caused by unhandled cases.
  • Flexibility: Enables matching complex structures, values, and types within a single expression.

Pattern matching is particularly useful when working with complex data structures or when you want to write clear and structured code to handle different possible cases or states.

Code example 1

In this example we have the Shape type that represents different kind of shapes:

type Shape =
  | { kind: "circle"; radius: number }
  | { kind: "square"; size: number };

And the calculateArea function that calculates the area of a shape:

const calculateArea = (shape: Shape) => {
  switch (shape.kind) {
    case "circle":
      return Math.PI * shape.radius * shape.radius;
    case "square":
      return shape.size * shape.size;
  }
};

What will happen when we add a new kind of shape, for example a triangle?

type Shape =
  | { kind: "circle"; radius: number }
  | { kind: "square"; size: number }
  | { kind: "triangle"; base: number; height: number };

We haven’t taken care of the triangle, but there are no warnings. Let’s fix that!

First, install ts-pattern:

yarn add ts-pattern

Then we will modify the calculateArea function to use pattern matching:

const calculateArea = (shape: Shape) =>
  match(shape)
    .with({ kind: "circle" }, (s) => Math.PI * s.radius * s.radius)
    .with({ kind: "square" }, (s) => s.size * s.size)
    .exhaustive();

By using the exhaustive command, we get a warning at compile time saying:

This expression is not callable.
Type 'NonExhaustiveError<{ kind: "triangle"; base: number; height: number; }>' has no call signatures.ts(2349)
That gives us a clue that we haven’t implemented the calculation of the triangle area.

Code example 2

In this example we have a Result type:
type Result = { status: string; value: number };

and a parseResult function:

const parseResult = (result: Result) => {
  if (result.status === "error") {
    return "Something went wrong";
  }

  if (result.status === "success") {
    if (result.value === 42) {
      return "The meaning of life";
    }

    if (result.value > 0) {
      return "Life is positive";
    }
  }

  return "Unknown status";
};

That is kind of hard to follow with all those nested if statements. We should try to use pattern matching here instead:

const parseResult = (result: Result) =>
  match(result)
    .with({ status: "error" }, () => "Something went wrong")
    .with({ status: "success", value: 42 }, () => "The meaning of life")
    .with(
      { status: "success", value: P.when((v) => v > 0) },
      () => "Life is positive"
    )
    .otherwise(() => "Unknown status");

That’s almost half the number of lines of code, not bad!

Summary

Simple, clear, and type-safe handling of complex structures in TypeScript

  • match: Starts the pattern matching with a value.
  • with: Defines patterns and functions to handle different cases.
  • exhaustive: Ensures that all cases are handled in a type-safe way.
  • otherwise: Catches all unmatched cases.
  • when: Adds extra conditions (guards) to matches.