Skip to Content
Patterns GuideP When Custom Predicates

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) }, ...)