Last Updated: 3/18/2026
P.when() - Custom Predicates
P.when() lets you define custom logic to check if a pattern should match. It accepts a predicate function that returns a truthy value when the pattern should match.
Basic Usage
import { match, P } from 'ts-pattern';
type Input = { score: number };
const output = match<Input>({ score: 10 })
.with(
{ score: P.when((score) => score < 5) },
() => '😞 Low score'
)
.with(
{ score: P.when((score) => score >= 5 && score < 8) },
() => '😐 Medium score'
)
.with(
{ score: P.when((score) => score >= 8) },
() => '🙂 High score'
)
.exhaustive();
console.log(output); // => '🙂 High score'Type Guards
You can narrow the type of your input by providing a Type Guard function :
const isString = (x: unknown): x is string => typeof x === 'string';
const isNumber = (x: unknown): x is number => typeof x === 'number';
const fn = (input: { id: number | string }) =>
match(input)
.with(
{ id: P.when(isString) },
(narrowed) => {
// narrowed: { id: string }
return `String ID: ${narrowed.id}`;
}
)
.with(
{ id: P.when(isNumber) },
(narrowed) => {
// narrowed: { id: number }
return `Number ID: ${narrowed.id}`;
}
)
.exhaustive();Examples
Age Validation
type User = { name: string; age: number };
const validateUser = (user: User) =>
match(user)
.with(
{ age: P.when((age) => age >= 18) },
(user) => ({ ...user, canVote: true })
)
.with(
{ age: P.when((age) => age < 18) },
(user) => ({ ...user, canVote: false })
)
.exhaustive();Complex Business Logic
type Order = {
items: Array<{ price: number }>;
discount?: number;
isPremium: boolean;
};
const calculateTotal = (order: Order) =>
match(order)
.with(
{
items: P.when((items) => items.length === 0),
},
() => 0
)
.with(
{
isPremium: true,
items: P.when((items) => {
const total = items.reduce((sum, item) => sum + item.price, 0);
return total > 100;
}),
},
(order) => {
const subtotal = order.items.reduce((sum, item) => sum + item.price, 0);
return subtotal * 0.8; // 20% premium discount
}
)
.otherwise((order) => {
const subtotal = order.items.reduce((sum, item) => sum + item.price, 0);
return subtotal - (order.discount || 0);
});Date Range Validation
const isWithinRange = (date: Date, start: Date, end: Date) =>
date >= start && date <= end;
const checkDate = (event: { date: Date; title: string }) => {
const startOfYear = new Date('2024-01-01');
const endOfYear = new Date('2024-12-31');
return match(event)
.with(
{
date: P.when((d) => isWithinRange(d, startOfYear, endOfYear)),
},
(event) => `${event.title} is in 2024`
)
.otherwise(() => 'Event outside 2024');
};Combining with Other Patterns
P.when + P.select
match(data)
.with(
{
value: P.when((v): v is number => typeof v === 'number' && v > 0).select(),
},
(value) => {
// value: number (and value > 0)
return `Positive number: ${value}`;
}
)
.otherwise(() => 'Not a positive number');P.when + P.not
match(status)
.with(
P.not(
P.when((s) => s === 'loading' || s === 'error')
),
(status) => `Status is: ${status}` // status is neither 'loading' nor 'error'
)
.otherwise(() => 'Loading or error state');Nested P.when
type Config = {
env: string;
debug: boolean;
port: number;
};
match(config)
.with(
{
env: P.when((e) => e === 'production'),
debug: P.when((d) => d === true),
},
() => {
console.warn('Debug mode enabled in production!');
}
)
.otherwise(() => {});Guard Function in .with()
Instead of using P.when() in your pattern, you can pass a guard function as the second argument to .with():
match(input)
.with(
{ status: 'loading' },
(input) => input.startTime + 2000 < Date.now(), // guard function
() => 'Timeout'
)
.otherwise(() => 'OK');This is equivalent to:
match(input)
.with(
{
status: 'loading',
startTime: P.when((t) => t + 2000 < Date.now()),
},
() => 'Timeout'
)
.otherwise(() => 'OK');Best Practices
Use Type Guards for Type Narrowing
// ✅ Good: Type guard provides type narrowing
const isPositive = (n: number): n is number => n > 0;
match(value)
.with(P.when(isPositive), (n) => {
// n is known to be positive
})
.exhaustive();Keep Predicates Pure
// ❌ Bad: Side effects in predicate
.with(
P.when((x) => {
console.log(x); // side effect!
return x > 0;
}),
...
)
// ✅ Good: Pure predicate
.with(
P.when((x) => x > 0),
(x) => {
console.log(x); // side effects in handler
return 'positive';
}
)Prefer Built-in Patterns When Available
// ❌ Less ideal
.with(P.when((x) => typeof x === 'string'), ...)
// ✅ Better
.with(P.string, ...)
// ❌ Less ideal
.with({ age: P.when((a) => a >= 18) }, ...)
// ✅ Better
.with({ age: P.number.gte(18) }, ...)