Skip to Content
Patterns GuideCollection Patterns Array Record Set Map

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