Last Updated: 3/18/2026
Collection Patterns (Array, Record, Set, Map)
TS-Pattern provides specialized patterns for matching collections of various types.
P.array(pattern)
Matches arrays where all elements match the sub-pattern.
Basic Usage
import { match, P } from 'ts-pattern';
type Input = { posts: Array<{ title: string; content: string }> };
match(input)
.with(
{
posts: P.array({ title: P.string, content: P.string }),
},
(data) => {
// data.posts: Array<{ title: string; content: string }>
return 'Valid posts array';
}
)
.otherwise(() => 'Invalid');Empty Arrays
match(arr)
.with([], () => 'Empty array')
.with(P.array(P.string), () => 'Non-empty string array')
.exhaustive();Nested Arrays
const data = {
users: [
{ id: 1, posts: [{ title: 'Post 1' }, { title: 'Post 2' }] },
],
};
match(data)
.with(
{
users: P.array({
id: P.number,
posts: P.array({ title: P.string }),
}),
},
() => 'Valid nested structure'
)
.otherwise(() => 'Invalid');Variadic Tuples
Match arrays with specific patterns at fixed positions:
// Non-empty array starting with a string
match(value)
.with([P.string, ...P.array(P.number)], (value) => {
// value: [string, ...number[]]
return 'String followed by numbers';
})
.exhaustive();
// Pattern after variadic
match(value)
.with([...P.array(P.string), P.number], (value) => {
// value: [...string[], number]
return 'Strings ending with number';
})
.exhaustive();
// Patterns on both sides
match(value)
.with([P.string, ...P.array(P.number), P.boolean], (value) => {
// value: [string, ...number[], boolean]
return 'String, numbers, then boolean';
})
.exhaustive();P.record(keyPattern, valuePattern)
Matches objects used as dictionaries (Record types) where all keys and values match their respective patterns.
Basic Usage
type Scores = Record<string, number>;
const scores: Scores = {
alice: 100,
bob: 85,
charlie: 92,
};
match(scores)
.with(
P.record(P.string, P.number),
(scores) => 'All user scores'
)
.otherwise(() => 'Invalid scores');Single Argument Form
When only matching values (assuming string keys):
const userProfiles = {
alice: { name: 'Alice', age: 25 },
bob: { name: 'Bob', age: 30 },
};
match(userProfiles)
.with(
P.record({ name: P.string, age: P.number }),
(profiles) => 'Valid user profiles'
)
.otherwise(() => 'Invalid');Selecting Keys or Values
const data = { a: 1, b: 2, c: 3 };
// Select all keys
const keys = match(data)
.with(
P.record(P.string.select(), P.number),
(keys) => keys // ['a', 'b', 'c']
)
.exhaustive();
// Select all values
const values = match(data)
.with(
P.record(P.string, P.number.select()),
(values) => values // [1, 2, 3]
)
.exhaustive();Nested Records
type NestedData = Record<string, Record<string, number>>;
const data: NestedData = {
group1: { a: 1, b: 2 },
group2: { c: 3, d: 4 },
};
match(data)
.with(
P.record(P.string, P.record(P.string, P.number)),
() => 'Valid nested structure'
)
.exhaustive();P.set(pattern)
Matches Sets where all elements match the sub-pattern.
Basic Usage
type Input = Set<string | number>;
const input: Input = new Set([1, 2, 3]);
match(input)
.with(P.set(1), () => 'Set contains only 1')
.with(P.set(P.string), () => 'Set of strings')
.with(P.set(P.number), () => 'Set of numbers')
.otherwise(() => 'Other');Complex Patterns
type User = { id: number; name: string };
const users = new Set<User>([
{ id: 1, name: 'Alice' },
{ id: 2, name: 'Bob' },
]);
match(users)
.with(
P.set({ id: P.number, name: P.string }),
(set) => 'Set of valid users'
)
.otherwise(() => 'Invalid');Set Operations
const checkSet = (s: Set<unknown>) =>
match(s)
.with(P.set(P.number.positive()), () => 'All positive numbers')
.with(P.set(P.string.minLength(3)), () => 'All strings 3+ chars')
.otherwise(() => 'Mixed or invalid');P.map(keyPattern, valuePattern)
Matches Maps where all keys and values match their respective patterns.
Basic Usage
type Input = Map<string, string | number>;
const input: Input = new Map([
['a', 1],
['b', 2],
['c', 3],
]);
match(input)
.with(
P.map(P.string, P.number),
() => "Map<string, number>"
)
.with(
P.map(P.string, P.string),
() => "Map<string, string>"
)
.otherwise(() => 'Other map type');Specific Key Matching
const config = new Map([
['host', 'localhost'],
['port', '8080'],
]);
match(config)
.with(
P.map(P.union('host', 'port'), P.string),
() => 'Valid config keys'
)
.otherwise(() => 'Invalid config');Complex Value Patterns
type UserMap = Map<number, { name: string; email: string }>;
const users: UserMap = new Map([
[1, { name: 'Alice', email: 'alice@example.com' }],
]);
match(users)
.with(
P.map(
P.number,
{ name: P.string, email: P.string.includes('@') }
),
() => 'Valid user map'
)
.otherwise(() => 'Invalid');Real-World Examples
API Response Validation
type ApiResponse = {
users: Array<{
id: number;
name: string;
tags: Set<string>;
metadata: Record<string, any>;
}>;
};
const validateResponse = (data: unknown) =>
match(data)
.with(
{
users: P.array({
id: P.number,
name: P.string.minLength(1),
tags: P.set(P.string),
metadata: P.record(P.string, P._),
}),
},
(data) => ({ valid: true, data })
)
.otherwise(() => ({ valid: false, error: 'Invalid response' }));Configuration Store
type Config = Map<string, string | number | boolean>;
const validateConfig = (config: Config) =>
match(config)
.with(
P.map(
P.string,
P.union(P.string, P.number, P.boolean)
),
() => 'Valid configuration'
)
.otherwise(() => 'Invalid configuration');Data Transformation
const scores = new Map([
['Alice', 95],
['Bob', 87],
['Charlie', 92],
]);
const result = match(scores)
.with(
P.map(P.string, P.number.gte(90)),
(map) => {
// All scores >= 90
return 'All high scores!';
}
)
.with(
P.map(P.string, P.number.gte(80)),
(map) => 'Good scores'
)
.otherwise(() => 'Mixed scores');Performance Considerations
- P.array: Checks every element against the pattern
- P.record: Checks every key-value pair
- P.set: Checks every element in the set
- P.map: Checks every key-value pair in the map
For large collections, consider using .with() guard functions for early exits:
match(largeArray)
.with(
P.array(P.string),
(arr) => arr.length > 1000, // guard function
() => 'Large string array'
)
.otherwise(() => 'Other');Type Safety
All collection patterns maintain full type safety:
const data: Array<string | number> = [1, 2, 3];
match(data)
.with(P.array(P.number), (arr) => {
// arr: number[]
return arr.map(n => n * 2);
})
.with(P.array(P.string), (arr) => {
// arr: string[]
return arr.map(s => s.toUpperCase());
})
.exhaustive();