Skip to Content
Patterns GuideString And Number Predicates

Last Updated: 3/18/2026


String and Number Predicates

TS-Pattern provides built-in predicates for common string and number validation patterns. These predicates help you match values based on their properties rather than exact equality.

String Predicates

P.string.startsWith()

Matches strings that start with a specific substring:

import { match, P } from 'ts-pattern'; const fn = (input: string) => match(input) .with(P.string.startsWith('TS'), () => '🎉 TypeScript!') .with(P.string.startsWith('JS'), () => '📜 JavaScript!') .otherwise(() => 'Other language'); console.log(fn('TS-Pattern')); // => '🎉 TypeScript!' console.log(fn('JavaScript-tools')); // => '📜 JavaScript!'

P.string.endsWith()

Matches strings that end with a specific substring:

const fn = (filename: string) => match(filename) .with(P.string.endsWith('.ts'), () => 'TypeScript file') .with(P.string.endsWith('.js'), () => 'JavaScript file') .with(P.string.endsWith('.json'), () => 'JSON file') .otherwise(() => 'Unknown file type');

P.string.includes()

Matches strings that contain a specific substring:

const fn = (message: string) => match(message) .with(P.string.includes('error'), () => '❌ Error message') .with(P.string.includes('warning'), () => '⚠️ Warning message') .with(P.string.includes('success'), () => '✅ Success message') .otherwise(() => 'ℹ️ Info message');

P.string.minLength()

Matches strings with at least a minimum number of characters:

const validatePassword = (password: string) => match(password) .with(P.string.minLength(8), () => 'Valid password') .otherwise(() => 'Password must be at least 8 characters');

P.string.maxLength()

Matches strings with at most a maximum number of characters:

const validateUsername = (username: string) => match(username) .with(P.string.maxLength(20), () => 'Valid username') .otherwise(() => 'Username too long');

P.string.length()

Matches strings with an exact number of characters:

const validateCode = (code: string) => match(code) .with(P.string.length(6), () => 'Valid 6-digit code') .otherwise(() => 'Code must be exactly 6 characters');

P.string.regex()

Matches strings against a regular expression:

const validateEmail = (email: string) => match(email) .with( P.string.regex(/^[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,}$/i), () => 'Valid email' ) .otherwise(() => 'Invalid email'); const parseInput = (input: string) => match(input) .with(P.string.regex(/^\d+$/), () => 'Digits only') .with(P.string.regex(/^[a-z]+$/i), () => 'Letters only') .with(P.string.regex(/^[a-z0-9]+$/i), () => 'Alphanumeric') .otherwise(() => 'Mixed characters');

Number Predicates

P.number.between()

Matches numbers within a range (inclusive):

const classifyScore = (score: number) => match(score) .with(P.number.between(90, 100), () => 'A - Excellent') .with(P.number.between(80, 89), () => 'B - Good') .with(P.number.between(70, 79), () => 'C - Average') .with(P.number.between(60, 69), () => 'D - Below Average') .otherwise(() => 'F - Fail'); console.log(classifyScore(95)); // => 'A - Excellent' console.log(classifyScore(85)); // => 'B - Good'

P.number.lt()

Matches numbers less than a maximum:

const checkTemperature = (temp: number) => match(temp) .with(P.number.lt(0), () => 'Freezing') .with(P.number.lt(20), () => 'Cold') .otherwise(() => 'Warm or hot');

P.number.lte()

Matches numbers less than or equal to a maximum:

match(age) .with(P.number.lte(12), () => 'Child') .with(P.number.lte(17), () => 'Teen') .otherwise(() => 'Adult');

P.number.gt()

Matches numbers greater than a minimum:

const checkPrice = (price: number) => match(price) .with(P.number.gt(1000), () => 'Premium') .with(P.number.gt(100), () => 'Standard') .otherwise(() => 'Budget');

P.number.gte()

Matches numbers greater than or equal to a minimum:

match(age) .with(P.number.gte(18), () => 'Can vote') .otherwise(() => 'Cannot vote');

P.number.int()

Matches integer values only:

const processNumber = (num: number) => match(num) .with(P.number.int(), (n) => `Integer: ${n}`) .otherwise((n) => `Decimal: ${n.toFixed(2)}`); console.log(processNumber(42)); // => 'Integer: 42' console.log(processNumber(3.14)); // => 'Decimal: 3.14'

P.number.finite()

Matches all finite numbers (excludes Infinity and -Infinity):

const safeCalculation = (result: number) => match(result) .with(P.number.finite(), (n) => `Result: ${n}`) .with(Infinity, () => 'Result is infinite') .with(-Infinity, () => 'Result is negative infinite') .exhaustive();

P.number.positive()

Matches positive numbers (> 0):

const validateQuantity = (qty: number) => match(qty) .with(P.number.positive(), (n) => `Valid quantity: ${n}`) .otherwise(() => 'Quantity must be positive');

P.number.negative()

Matches negative numbers (< 0):

const checkBalance = (balance: number) => match(balance) .with(P.number.negative(), (b) => `Overdraft: ${Math.abs(b)}`) .with(0, () => 'Balance is zero') .with(P.number.positive(), (b) => `Available: ${b}`) .exhaustive();

BigInt Predicates

The same predicates are available for P.bigint:

match(value) .with(P.bigint.between(0n, 100n), () => 'Between 0 and 100') .with(P.bigint.gt(100n), () => 'Greater than 100') .with(P.bigint.positive(), () => 'Positive') .with(P.bigint.negative(), () => 'Negative') .exhaustive();

Combining Predicates

Chaining with P.select()

match(user) .with( { age: P.select(P.number.gte(18)) }, (age) => `Adult: ${age} years old` ) .otherwise(() => 'Minor');

Using Multiple Predicates

type User = { username: string; age: number; }; const validateUser = (user: User) => match(user) .with( { username: P.string.minLength(3).maxLength(20), age: P.number.between(13, 120) }, () => 'Valid user' ) .otherwise(() => 'Invalid user');

Real-World Examples

HTTP Status Code Handler

const handleStatus = (code: number) => match(code) .with(P.number.between(200, 299), () => 'Success') .with(P.number.between(300, 399), () => 'Redirect') .with(P.number.between(400, 499), () => 'Client Error') .with(P.number.between(500, 599), () => 'Server Error') .otherwise(() => 'Unknown Status');

Email Validator

const validateEmail = (email: string) => match(email) .with( P.string .includes('@') .regex(/^[^\s@]+@[^\s@]+\.[^\s@]+$/), () => ({ valid: true, email }) ) .otherwise(() => ({ valid: false, error: 'Invalid email' }));

File Size Formatter

const formatFileSize = (bytes: number) => match(bytes) .with(P.number.lt(1024), (b) => `${b} B`) .with(P.number.lt(1024 * 1024), (b) => `${(b / 1024).toFixed(2)} KB`) .with(P.number.lt(1024 * 1024 * 1024), (b) => `${(b / (1024 * 1024)).toFixed(2)} MB` ) .otherwise((b) => `${(b / (1024 * 1024 * 1024)).toFixed(2)} GB`);