NewYour coding agent can read the release notes before it upgrades.Set up the MCP server →
npm · #3978 most downloaded on npm
The exhaustive Pattern Matching library for TypeScript.
Last release 11 months ago
26 Oct 2025
Ships fairly regularly
a new release about every 2 months
Most releases are documented
notes for 47 of the last 60 stable releases
Nothing withdrawn
no release was ever pulled
6 years old
159 releases · first in 2020
One column per quarter.
Nothing published for this version
Patch release containing a few runtime performance improvements:
Patch release containing a few runtime performance improvements:
Builder class internally in match expression to rely on prototypal inheritance instead of defining method every time .with is called..with(pattern, handler) case separately from .with(...pattern, handler) to avoid iterating on params and make it faster.type-specific wildcard patterns have moved from __. to a new Pattern qualified module, also exported as P by ts-pattern.
type-specific wildcard patterns have moved from __.<pattern> to a new Pattern qualified module, also exported as P by ts-pattern.
- import { match, __ } from 'ts-pattern';
+ import { match, Pattern } from 'ts-pattern';
const toString = (value: string | number) =>
match(value)
- .with(__.string, (v) => v)
- .with(__.number, (v) => `${v}`)
+ .with(Pattern.string, (v) => v)
+ .with(Pattern.number, (v) => `${v}`)
.exhaustive();
or
- import { match, __ } from 'ts-pattern';
+ import { match, P } from 'ts-pattern';
const toString = (value: string | number) =>
match(value)
- .with(__.string, (v) => v)
- .with(__.number, (v) => `${v}`)
+ .with(P.string, (v) => v)
+ .with(P.number, (v) => `${v}`)
.exhaustive();
__The top level __ export was moved to P._ and P.any:
- import { match, __ } from 'ts-pattern';
+ import { match, P } from 'ts-pattern';
const toString = (value: string | number) =>
match(value)
- .with(__, (v) => `${v}`)
+ .with(P._, (v) => `${v}`)
// OR
+ .with(P.any, (v) => `${v}`)
.exhaustive();
select(), not(), when()Function to create patterns have been moved to the P module.
- import { match, select, not, when } from 'ts-pattern';
+ import { match, P } from 'ts-pattern';
const toString = (value: number) =>
match(value)
- .with({ prop: select() }, (v) => `${v}`)
+ .with({ prop: P.select() }, (v) => `${v}`)
- .with({ prop: not(10) }, (v) => `${v}`)
+ .with({ prop: P.not(10) }, (v) => `${v}`)
- .with({ prop: when((x) => x < 5) }, (v) => `${v}`)
+ .with({ prop: P.when((x) => x < 5) }, (v) => `${v}`)
.exhaustive();
Pattern typethe Pattern type which used to be exported at the toplevel is now accessible at P.Pattern.
- import { match, Pattern } from 'ts-pattern';
+ import { match, P } from 'ts-pattern';
- const pattern: Pattern<number> = P.when(x => x > 2);
+ const pattern: P.Pattern<number> = P.when(x => x > 2);
The syntax for matching on a list of elements with an unknown length has changed from [subpattern] to P.array(subpattern).
Example:
- import { match, __ } from 'ts-pattern';
+ import { match, P } from 'ts-pattern';
const parseUsers = (response: unknown) =>
match(response)
- .with({ data: [{ name: __.string }] }, (users) => users)
+ .with({ data: P.array({ name: P.string }) }, (users) => users)
.otherwise(() => []);
Now [subpattern] matches arrays with 1 element in them. This is more consistent with native language features, like destructuring assignement and is overall more intuitive. This will resolve #69, #62 and #46.
The __.NaN pattern has been replaced by simply using the NaN value in the pattern:
match<number>(NaN)
- .with(__.NaN, () => "this is not a number")
+ .with(NaN, () => "this is not a number")
.otherwise((n) => n);
Here is the list of all new features which have been added in TS-Pattern v4.
P.array(pattern)To match an array of elements, you can now use P.array:
import { match, P } from 'ts-pattern';
const responsePattern = {
data: P.array({
id: P.string,
post: P.array({
title: P.string,
content: P.string,
}),
}),
};
fetchSomething().then((value: unknown) =>
match(value)
.with(responsePattern, (value) => {
// value: { data: { id: string, post: { title: string, content: string }[] }[] }
return value;
})
.otherwise(() => {
throw new Error('unexpected response');
})
);
P.optional(pattern)If you want one of the keys of your pattern to be optional, you can now use P.optional(subpattern).
If you P.select() something in an optional pattern, it's type will be infered as T | undefined.
import { match, P } from 'ts-pattern';
const doSomethingWithUser = (user: User | Org) =>
match(user)
.with(
{
type: 'user',
detail: {
bio: P.optional(P.string),
socialLinks: P.optional({
twitter: P.select(),
}),
},
},
(twitterLink, value) => {
// twitterLink: string | undefined
/**
* value.detail: {
* bio?: string,
* socialLinks?: {
* twitter: string
* }
* }
**/
}
)
.otherwise(() => {
throw new Error('unexpected response');
});
P.union(...patterns) and P.intersection(...patterns) combine several patterns into a single one, either by checking that one of them match the input (p.union) or all of them match it (P.intersection).
P.union(...patterns)type Input =
| { type: 'a'; value: string }
| { type: 'b'; value: number }
| {
type: 'c';
value:
| { type: 'd'; value: boolean }
| { type: 'e'; value: string[] }
| { type: 'f'; value: number[] };
};
const f = (input: Input) =>
match(input)
.with(
{ type: P.union('a', 'b') },
// x: { type: 'a'; value: string } | { type: 'b'; value: number }
(x) => 'branch 1'
)
.with(
// P.union can take any subpattern:
{
type: 'c',
value: { value: P.union(P.boolean, P.array(P.string)) },
},
(x) => 'branch 2' // x.value.value: boolean | string[]
)
.with({ type: 'c', value: { type: 'f' } }, () => 'branch 3')
.exhaustive();
P.intersection(...patterns)class A {
constructor(public foo: 'bar' | 'baz') {}
}
class B {
constructor(public str: string) {}
}
const f = (input: { prop: A | B }) =>
match(input)
.with(
{ prop: P.intersection(P.instanceOf(A), { foo: 'bar' }) },
// prop: A & { foo: 'bar' }
({ prop }) => 'branch 1'
)
.with(
{ prop: P.intersection(P.instanceOf(A), { foo: 'baz' }) },
// prop: A & { foo: 'baz' }
({ prop }) => 'branch 2'
)
.with(
{ prop: P.instanceOf(B) },
// prop: B
({ prop }) => 'branch 3'
)
.exhaustive();
P.select() now can take a subpattern and match only what the subpattern matches:
type Img = { type: 'img'; src: string };
type Text = { type: 'text'; content: string; length: number };
type User = { type: 'user'; username: string };
type Org = { type: 'org'; orgId: number };
const post = (input: { author: User | Org; content: Text | Img }) =>
match(input)
.with(
{ author: P.select({ type: 'user' }) },
// user: User
(user) => {}
)
.with(
{
// This also works with named selections
author: P.select('org', { type: 'org' }),
content: P.select('text', { type: 'text' }),
},
// org: Org, text: Text
({ org, text }) => {}
)
.otherwise(() => {
// ...
});
P.infer<typeof pattern>TS-Pattern is pretty handy for parsing unknown payloads like HTTP responses. You can write a pattern for the shape you are expecting, and then use isMatching(pattern, response) to make sure the response has the correct shape.
One limitation TS-Pattern had in its previous version was that it did not provide a way to get the TypeScript type of the value a given pattern matches. This is what P.infer<typeof pattern> does :)
const postPattern = {
title: P.string,
description: P.optional(P.string),
content: P.string,
likeCount: P.number,
};
type Post = P.infer<typeof postPattern>;
// Post: { title: string, description?: string, content: string, likeCount: number }
const userPattern = {
name: P.string,
postCount: P.number,
bio: P.optional(P.string),
posts: P.optional(P.array(postPattern)),
};
type User = P.infer<typeof userPattern>;
// User: { name: string, postCount: number, bio?: string, posts?: Post[] }
const isUserList = isMatching(P.array(userPattern));
const res = await fetchUsers();
if (isUserList(res)) {
// res: User
}
P.symbolP.symbol is a wildcard pattern matching any symbol.
match(Symbol('Hello'))
.with(P.symbol, () => 'this is a symbol!')
.exhaustive();
P.bigintP.bigint is a wildcard pattern matching any bigint.
match(200n)
.with(P.bigint, () => 'this is a bigint!')
.exhaustive();
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
This fixes a type inference bug impacting handler functions with explicit type annotations.
This fixes a type inference bug impacting handler functions with explicit type annotations.
It used to be possible to annotate the handler function with an invalid type annotation. Thanks to this commit, it no longer type-checks https://github.com/gvergnaud/ts-pattern/commit/2d750742d95d38e4cab5ff1f2915050f2081441a.
See the related issue for more details: #73
This release fixes a type inference bug specific to Error sub classes. See the related issue for more details: https://github.com/gvergnaud/ts-pattern
This release fixes a type inference bug specific to Error sub classes. See the related issue for more details: https://github.com/gvergnaud/ts-pattern/issues/63
Nothing published for this version
Nothing published for this version
Nothing published for this version
This patch contains some compile time perf improvements.
This patch contains some compile time perf improvements.
@ahejlsberg recently implemented tail call elimination for recursive conditional types (https://github.com/microsoft/TypeScript/pull/45711). This release is preparation work to take advantage of this new feature by making most type helper functions tail recursive. From the non scientific tests I made on my machine, this also improves the compilation time of the tests/ folder quite significantly on our current TS version (4.4). Compilation is ~ 20% faster.
Add a __.NaN pattern, matching only NaNs values. Thanks @mhintz for adding this
Add a __.NaN pattern, matching only NaNs values. Thanks @mhintz for adding this
const res = match<number | null>(NaN)
.with(null, () => 'null!')
.with(__.NaN, () => 'NaN!')
.with(__.number, (x) => 'a number!')
.exhaustive();
console.log(res)
// => 'NaN!'
Update the __.number pattern to also match on NaN values.
Since NaN has type number in TypeScript, there is no way to distinguish a NaN from a regular number at the type level. This was causing an issue where .exhaustive() considered all numbers handled by the __.number pattern even though NaN wasn't matched by it, resulting in possible runtime errors.
const res = match<number | null>(NaN)
.with(null, () => 'null!')
.with(__.number, (x) => 'a number!')
// This used to throw at runtime because NaN wasn't matched by __.number
.exhaustive();
console.log(res)
// => 'a number!'
Fix a bug with exhaustiveness checking, where a case could wrongfully be considered handled even if the pattern was containing additional keys that ar
type Person = {
sex: "Male" | "Female";
age: "Adult" | "Child";
};
function summary(person: Person): string {
return (
match(person)
// Typo – "agf" should be "age"
.with({ sex: "Female", agf: "Adult" }, () => "Woman")
.with({ sex: "Female", age: "Child" }, () => "Girl")
.with({ sex: "Male", age: "Adult" }, () => "Man")
.with({ sex: "Male", age: "Child" }, () => "Boy")
// Bugfix: This pattern is no longer considered exhaustive!
.exhaustive()
);
}
.otherwise now passes the input value to the handler function. This can be useful when your default handler is a statically defined function and you w
.otherwise now passes the input value to the handler function. This can be useful when your default handler is a statically defined function and you want to avoid writing an intermediary anonymous function passing the input to it. PR #37 from @oguimbal 🙏Error objects. To save some compilation time, deep pattern matching on builtin objects is disabled, but it turns out this can be pretty handy to pattern match on native Error objects because some libs (like @apollo/client) enhance them with additional properties.Nothing published for this version
Fix a bug reported in issue #40, where object pattern containing a single wildcard would match any object, even if the key used by the pattern wasn't
Fix a bug reported in issue #40, where object pattern containing a single wildcard would match any object, even if the key used by the pattern wasn't present on the input object.
type Id = { teamId: number } | { storeId: number };
const selectedId: Id = { teamId: 1 };
match<Id>(selectedId)
.with({ storeId: __ }, () => "storeId")
.with({ teamId: __ }, () => "teamId")
.exhaustive()
// Now outputs "teamId" instead of "storeId"
This minor release adds 3 new features to the lib.
This minor release adds 3 new features to the lib.
__.nullishA new wildcard pattern, matching null | undefined. Thanks a lot @m-rutter for implementing this and fixing some issues with pattern inference along the way! 🎉
import { match, __ } from 'ts-pattern';
const input = null;
const output = match<number | string | boolean | null | undefined>(input)
.with(__.string, () => 'it is a string!')
.with(__.number, () => 'it is a number!')
.with(__.boolean, () => 'it is a boolean!')
.with(__.nullish, () => 'it is either null or undefined!')
.with(null, () => 'it is null!')
.with(undefined, () => 'it is undefined!')
.run();
console.log(output);
// => 'it is either null or undefined!'
instanceOfA new function creating a pattern, checking if a value is an instance of a particular class. This feature was requests in #24, it was possible implement it in userland using a when pattern, but the types were a bit tricky so I thought it made sense to add first class support for this.
import { match, instanceOf } from 'ts-pattern';
class A {
a = 'a';
}
class B {
b = 'b';
}
const output = match<{ value: A | B }>({ value: new A() })
.with({ value: instanceOf(A) }, (a) => 'instance of A!')
.with({ value: instanceOf(B) }, (b) => 'instance of B!')
.exhaustive();
console.log(output);
// => 'instance of A!'
isMatchingA helper function to create a type guard function from a pattern. This can be really useful when making a runtime type assertion, for instance if you want to check that an API response matches what you are expecting.
import { isMatching, __ } from 'ts-pattern';
const isBlogPost = isMatching({
title: __.string,
content: __.string,
});
const res: unknown = await fetch(...)
if (isBlogPost(res)) {
// res: { title: string, content: string }
}
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
This release features an important performance improvement: ts-pattern now checks if the pattern matches and performs selections coming from select()
This release features an important performance improvement: ts-pattern now checks if the pattern matches and performs selections coming from select() patterns all in one pass.
The performance difference won't be very significant in most use cases, but it could be noticable when frequently pattern matching using deep patterns.
Type inference improvement when combining a when(predicate) pattern with a not() pattern.
Bug Fixes:
when(predicate) pattern with a not() pattern.Performance improvements:
Nothing published for this version
Some compile-time performance improvement on the ExtractPreciseValue type, inferring the type matched by a given pattern.
Some compile-time performance improvement on the ExtractPreciseValue type, inferring the type matched by a given pattern.
when not provided, ts-pattern tries to infer the Output type in Match (the type of a match expression). Until now, the Output type was inferred from t
when not provided, ts-pattern tries to infer the Output type in Match<Input, Output> (the type of a match expression). Until now, the Output type was inferred from the return type of the first branch:
declare let n: number;
let res = match(n)
.with(2, () => "two") // we return a string, the type of our match expression becomes `Match<number, string>`
.with(__, () => null) // Type error! `null` isn't of type `string`
.exhaustive();
This behavior was forcing us to specify the output type to match with match<number, string>(n).
With this release, ts-pattern is a bit smarter about it and automatically infers the Output as the union of the return types of all branches:
declare let n: number;
let res = match(n)
.with(2, () => "two")
.with(__, () => null)
.exhaustive();
// res is inferred as `string | null`
Nothing published for this version
for readonly tuples, forbid passing tuple patterns with a different size than the input to .with()
a few small improvements:
.with()match(), .with(), .when() and .exhaustive.Nothing published for this version
Bug fix on exhaustive checking for readonly tuple patterns containing a wildcard pattern. These patterns were never considered exhaustive, unless usin
Bug fix on exhaustive checking for readonly tuple patterns containing a wildcard pattern. These patterns were never considered exhaustive, unless using __ to match the whole input. Examples of impacted patterns: readonly [__, "hello", 2] or readonly [{ x: 0 }, __].
Fixes a small type inference bug that could occur with input types with optional properties. See this issue for more detail https://github.com/gvergna
Fixes a small type inference bug that could occur with input types with optional properties. See this issue for more detail https://github.com/gvergnaud/ts-pattern/issues/20
This versions introduces a few breaking change for the sake of a better general developer experience.
This versions introduces a few breaking change for the sake of a better general developer experience.
.exhaustive() now ends the pattern matching expressionconst f = (x: 1 | 2 | 3) =>
match(x)
.exhaustive()
.with(1, () => 'one')
.with(2, () => 'two')
.with(3, () => 'three')
.run();
const f = (x: 1 | 2 | 3) =>
match(x)
.with(1, () => 'one')
.with(2, () => 'two')
.with(3, () => 'three')
.exhaustive();
type Value = { type: 'vec2', x: number, y: number } | { type: 'number', value: number }
const f = (x: [Value, Value]) =>
match(x)
.with([{ type: 'vec2', x: select('x'), y: select('y') }, __], (_, { x, y }) => ...)
...
.run();
type Value = { type: 'vec2', x: number, y: number } | { type: 'number', value: number }
const f = (x: [Value, Value]) =>
match(x)
.with([{ type: 'vec2', x: select('x'), y: select('y') }, __], ({ x, y }) => ...)
...
.exhaustive();
ts-pattern now supports anonymous selection for when you want to extract a single value from your pattern:
// Not possible
type Value = { type: 'vec2', x: number, y: number } | { type: 'number', value: number }
const f = (x: Value) =>
match(x)
.with({ type: 'number', value: select() }, (value) => /* value: number */)
// you can't have several anonymous `select()` in the same pattern. This is a type error:
.with({ type: 'vec2', x: select(), y: select() }, (value) => /* value: SeleveralAnonymousSelectError */)
.exhaustive();
when clauses within .exhaustive() match expressionswhen(predicate) patterns and match(...).when(predicate) are now permitted within .exhaustive() match expressions.
// Not possible
If your predicate is a type guard function, the case will be considered handled:
type Input = 'a' | 'b'
match<Input>('a')
.when((x): x is 'a' => x === 'a' , () => {...})
.when((x): x is 'b' => x === 'b' , () => {...})
.exhaustive(); // This compiles
match<Input>('a')
.when((x): x is 'a' => x === 'a' , () => {...})
// This doesn't compiles
.exhaustive();
But if your predicate isn't a type guard, exhaustive checking will consider that this clause never matches anything:
match<Input>('a')
.when((x): x is 'a' => x === 'a' , () => {...})
.when(x => x === 'b' , () => {...})
// This doesn't compiles, because ts-pattern has no way to know that the 'b' case is handled
.exhaustive();
It works similarily with the when() helper function:
type input = { type: 'success', data: string[] } | { type: 'error' }
match(input)
.with({ type: 'success', data: when(xs => xs.length > 0) }, () => {...})
.with({ type: 'error' }, () => {...})
// this doesn't compile, { type: 'success' } with an empty data array is not handled
.exhaustive();
match(input)
.with({ type: 'success', data: when(xs => xs.length > 0) }, () => {...})
.with({ type: 'success' }, () => {...})
.with({ type: 'error' }, () => {...})
.exhaustive(); // this compiles
Nothing published for this version
Nothing published for this version
Nothing published for this version
This versions introduces a few breaking change for the sake of a better general developer experience.
This versions introduces a few breaking change for the sake of a better general developer experience.
.exhaustive() now ends the pattern matching expressionconst f = (x: 1 | 2 | 3) =>
match(x)
.exhaustive()
.with(1, () => 'one')
.with(2, () => 'two')
.with(3, () => 'three')
.run();
const f = (x: 1 | 2 | 3) =>
match(x)
.with(1, () => 'one')
.with(2, () => 'two')
.with(3, () => 'three')
.exhaustive();
type Value = { type: 'vec2', x: number, y: number } | { type: 'number', value: number }
const f = (x: [Value, Value]) =>
match(x)
.with([{ type: 'vec2', x: select('x'), y: select('y') }, __], (_, { x, y }) => ...)
...
.run();
type Value = { type: 'vec2', x: number, y: number } | { type: 'number', value: number }
const f = (x: [Value, Value]) =>
match(x)
.with([{ type: 'vec2', x: select('x'), y: select('y') }, __], ({ x, y }) => ...)
...
.exhaustive();
ts-pattern now supports anonymous selection for when you want to extract a single value from your pattern:
// Not possible
type Value = { type: 'vec2', x: number, y: number } | { type: 'number', value: number }
const f = (x: Value) =>
match(x)
.with({ type: 'number', value: select() }, (value) => /* value: number */)
// you can't have several anonymous `select()` in the same pattern. This is a type error:
.with({ type: 'vec2', x: select(), y: select() }, (value) => /* value: SeleveralAnonymousSelectError */)
.exhaustive();
when clauses within .exhaustive() match expressionswhen(predicate) patterns and match(...).when(predicate) are now permitted within .exhaustive() match expressions.
// Not possible
If your predicate is a type guard function, the case will be considered handled:
type Input = 'a' | 'b'
match<Input>('a')
.when((x): x is 'a' => x === 'a' , () => {...})
.when((x): x is 'b' => x === 'b' , () => {...})
.exhaustive(); // This compiles
match<Input>('a')
.when((x): x is 'a' => x === 'a' , () => {...})
// This doesn't compiles
.exhaustive();
But if your predicate isn't a type guard, exhaustive checking will consider that this clause never matches anything:
match<Input>('a')
.when((x): x is 'a' => x === 'a' , () => {...})
.when(x => x === 'b' , () => {...})
// This doesn't compiles, because ts-pattern has no way to know that the 'b' case is handled
.exhaustive();
It works similarily with the when() helper function:
type input = { type: 'success', data: string[] } | { type: 'error' }
match(input)
.with({ type: 'success', data: when(xs => xs.length > 0) }, () => {...})
.with({ type: 'error' }, () => {...})
// this doesn't compile, { type: 'success' } with an empty data array is not handled
.exhaustive();
match(input)
.with({ type: 'success', data: when(xs => xs.length > 0) }, () => {...})
.with({ type: 'success' }, () => {...})
.with({ type: 'error' }, () => {...})
.exhaustive(); // this compiles
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
when(predicate) patterns and match(...).when(predicate) are now permitted within .exhaustive() match expressions.
.exhaustive()when(predicate) patterns and match(...).when(predicate) are now permitted within .exhaustive() match expressions.
If your predicate is a type guard function, the case will be considered handled:
type Input = 'a' | 'b'
match<Input>('a')
.exhaustive()
.when((x): x is 'a' => x === 'a' , () => {...})
.when((x): x is 'b' => x === 'b' , () => {...})
// This compiles
.run();
match<Input>('a')
.exhaustive()
.when((x): x is 'a' => x === 'a' , () => {...})
// This doesn't compiles
.run();
But if your predicate isn't a type guard, exhaustive checking will consider that this clause never matches anything:
match<Input>('a')
.exhaustive()
.when((x): x is 'a' => x === 'a' , () => {...})
.when(x => x === 'b' , () => {...})
// This doesn't compiles, because ts-pattern has no way to know that the 'b' case is handled
.run();
It works similarily with the when() helper function:
type input = { type: 'success', data: string[] } | { type: 'error' }
match(input)
.exhaustive()
.with({ type: 'success', data: when(xs => xs.length > 0) }, () => {...})
.with({ type: 'error' }, () => {...})
// this doesn't compile, { type: 'success' } with an empty data array is not handled
.run();
match(input)
.exhaustive()
.with({ type: 'success', data: when(xs => xs.length > 0) }, () => {...})
.with({ type: 'success' }, () => {...})
.with({ type: 'error' }, () => {...})
// this compiles
.run();
A few bug fixes regarding type inference:
A few bug fixes regarding type inference:
"hello" or 2) where inferred as string and number. When patterns were containing literals, the pattern matching expression was considered exhaustive, even though it wasn't.any: If an any type was contained in your input data structure, ts-pattern was sometimes unable to infer which branch of the input type was matching your pattern.Your coding agent can read these notes before it upgrades. Set up the MCP server →