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`);