Last Updated: 3/6/2026
Getting Started with TS-Pattern
Installation
Install ts-pattern using your preferred package manager:
npm install ts-patternOr with other package managers:
pnpm add ts-pattern
# OR
yarn add ts-pattern
# OR
bun add ts-pattern
# OR
npx jsr add @gabriel/ts-patternExample: A State Reducer with ts-pattern
Let’s create a state reducer for a frontend application that fetches some data.
Our application can be in four different states: idle, loading, success and error. Depending on which state we are in, some events can occur. Here are all the possible types of event our application can respond to: fetch, success, error and cancel.
type State =
| { status: 'idle' }
| { status: 'loading'; startTime: number }
| { status: 'success'; data: string }
| { status: 'error'; error: Error };
type Event =
| { type: 'fetch' }
| { type: 'success'; data: string }
| { type: 'error'; error: Error }
| { type: 'cancel' };Even though our application can handle 4 events, only a subset of these events make sense for each given state. For instance we can only cancel a request if we are currently in the loading state.
To avoid unwanted state changes that could lead to bugs, we want our state reducer function to branch on both the state and the event, and return a new state.
This is a case where match really shines. Instead of writing nested switch statements, we can use pattern matching to simultaneously check the state and the event object:
import { match, P } from 'ts-pattern';
const reducer = (state: State, event: Event) =>
match([state, event])
.returnType<State>()
.with(
[{ status: 'loading' }, { type: 'success' }],
([_, event]) => ({ status: 'success', data: event.data })
)
.with(
[{ status: 'loading' }, { type: 'error', error: P.select() }],
(error) => ({ status: 'error', error })
)
.with(
[{ status: P.not('loading') }, { type: 'fetch' }],
() => ({ status: 'loading', startTime: Date.now() })
)
.with(
[
{
status: 'loading',
startTime: P.when((t) => t + 2000 < Date.now()),
},
{ type: 'cancel' },
],
() => ({ status: 'idle' })
)
.with(P._, () => state)
.exhaustive();Understanding the Code
match(value)
match takes a value and returns a builder on which you can add your pattern matching cases.
match([state, event]).returnType()
.returnType is an optional method that forces all following code-branches to return a value of a specific type.
.returnType<State>()Here, we use this method to make sure all branches return a valid State object.
.with(pattern, handler)
The first argument is the pattern: the shape of value you expect for this branch. The second argument is the handler function: the code branch that will be called if the input value matches the pattern.
.with(
[{ status: 'loading' }, { type: 'success' }],
([state, event]) => ({
// `state` is inferred as { status: 'loading' }
// `event` is inferred as { type: 'success', data: string }
status: 'success',
data: event.data,
})
)P.select(name?)
P.select() lets you extract a piece of your input value and inject it into your handler.
.with(
[
{ status: 'loading' },
{ type: 'error', error: P.select() }
],
(error) => ({ status: 'error', error })
)Since we didn’t pass any name to P.select(), it will inject the event.error property as first argument to the handler function.
You can also use named selections:
.with(
[
{ status: 'success', data: P.select('prevData') },
{ type: 'error', error: P.select('err') }
],
({ prevData, err }) => {
// Do something with (prevData: string) and (err: Error).
}
)P.not(pattern)
If you need to match on everything but a specific value, you can use a P.not() pattern:
.with(
[{ status: P.not('loading') }, { type: 'fetch' }],
() => ({ status: 'loading' })
)P.when() and Guard Functions
Sometimes, we need to make sure our input value respects a condition that can’t be expressed by a pattern. For these cases, we can use guard functions.
Using P.when(predicate)
.with(
[
{
status: 'loading',
startTime: P.when((t) => t + 2000 < Date.now()),
},
{ type: 'cancel' },
],
() => ({ status: 'idle' })
)Passing a guard function to .with(…)
.with optionally accepts a guard function as second parameter:
.with(
[{ status: 'loading' }, { type: 'cancel' }],
([state, event]) => state.startTime + 2000 < Date.now(),
() => ({ status: 'idle' })
)The P._ Wildcard
P._ will match any value. You can use it either at the top level, or within another pattern.
.with(P._, () => state)
// You could also use it inside another pattern:
.with([P._, P._], () => state).exhaustive(), .otherwise() and .run()
.exhaustive() executes the pattern matching expression, and returns the result. It also enables exhaustiveness checking, making sure we don’t forget any possible case.
.exhaustive();Alternatively, you can use .otherwise(), which takes a handler function returning a default value:
.otherwise(() => state);