Last Updated: 3/18/2026
Objects, Tuples, and Literals
These are the fundamental patterns for matching structured data in TS-Pattern.
Literal Patterns
Literals are primitive JavaScript values that match exactly.
Supported Literal Types
- Numbers:
2,3.14,-5 - Strings:
'hello',"world" - Booleans:
true,false - BigInts:
20n,100n - Symbols:
Symbol.for('key') - Special values:
null,undefined,NaN
Examples
import { match } from 'ts-pattern';
const input: unknown = 2;
const output = match(input)
.with(2, () => 'number: two')
.with(true, () => 'boolean: true')
.with('hello', () => 'string: hello')
.with(undefined, () => 'undefined')
.with(null, () => 'null')
.with(NaN, () => 'number: NaN')
.with(20n, () => 'bigint: 20n')
.otherwise(() => 'something else');
console.log(output); // => 'number: two'String Literals in Union Types
type Status = 'idle' | 'loading' | 'success' | 'error';
const getStatusMessage = (status: Status) =>
match(status)
.with('idle', () => 'Not started')
.with('loading', () => 'In progress...')
.with('success', () => '✓ Complete')
.with('error', () => '✗ Failed')
.exhaustive();Object Patterns
Object patterns match if the input is an object containing all properties defined in the pattern, and each property matches its sub-pattern.
Basic Object Matching
type Input =
| { type: 'user'; name: string }
| { type: 'image'; src: string }
| { type: 'video'; seconds: number };
const input: Input = { type: 'user', name: 'Gabriel' };
const output = match(input)
.with({ type: 'image' }, () => 'image')
.with({ type: 'video', seconds: 10 }, () => 'video of 10 seconds')
.with({ type: 'user' }, ({ name }) => `user: ${name}`)
.exhaustive();
console.log(output); // => 'user: Gabriel'Partial Matching
Object patterns only need to match some properties. Other properties are ignored:
type User = { id: number; name: string; email: string; age: number };
const user: User = { id: 1, name: 'Alice', email: 'alice@example.com', age: 30 };
match(user)
.with({ name: 'Alice' }, (u) => {
// u is still the full User object
// We only matched on name, but all properties are available
console.log(u.email); // 'alice@example.com'
})
.exhaustive();Nested Objects
type Post = {
author: {
name: string;
profile: {
verified: boolean;
};
};
content: string;
};
match(post)
.with(
{
author: {
profile: { verified: true },
},
},
(post) => 'Verified author'
)
.otherwise(() => 'Unverified');Mixing Literals and Wildcards
import { match, P } from 'ts-pattern';
type Response = {
status: 'success' | 'error';
code: number;
data: any;
};
match(response)
.with({ status: 'success', code: 200 }, () => 'OK')
.with({ status: 'success', code: P.number }, () => 'Success (non-200)')
.with({ status: 'error' }, () => 'Error occurred')
.exhaustive();Tuple Patterns
In TypeScript, tuples are arrays with a fixed number of elements of specific types. Tuple patterns match arrays of the same length where each element matches its sub-pattern.
Basic Tuple Matching
import { match, P } from 'ts-pattern';
type Operation =
| [number, '+', number]
| [number, '-', number]
| [number, '*', number]
| ['-', number];
const input: Operation = [3, '*', 4];
const output = match(input)
.with([P._, '+', P._], ([x, , y]) => x + y)
.with([P._, '-', P._], ([x, , y]) => x - y)
.with([P._, '*', P._], ([x, , y]) => x * y)
.with(['-', P._], ([, x]) => -x)
.exhaustive();
console.log(output); // => 12Tuple with Specific Values
type Command = ['move', number, number] | ['jump', number];
match(command)
.with(['move', 0, 0], () => 'Stay in place')
.with(['move', P.number, P.number], ([, x, y]) => `Move to (${x}, ${y})`)
.with(['jump', P.number], ([, height]) => `Jump ${height} units`)
.exhaustive();Destructuring in Handlers
type Point = [x: number, y: number, z: number];
const point: Point = [1, 2, 3];
match(point)
.with([0, 0, 0], () => 'Origin')
.with([P._, 0, 0], ([x]) => `On X axis: ${x}`)
.with([0, P._, 0], ([, y]) => `On Y axis: ${y}`)
.with([0, 0, P._], ([, , z]) => `On Z axis: ${z}`)
.with([P._, P._, P._], ([x, y, z]) => `Point (${x}, ${y}, ${z})`)
.exhaustive();Nested Tuples
type Matrix = [[number, number], [number, number]];
const matrix: Matrix = [[1, 2], [3, 4]];
match(matrix)
.with([[0, 0], [0, 0]], () => 'Zero matrix')
.with(
[[1, 0], [0, 1]],
() => 'Identity matrix'
)
.with(
[[P.number, P.number], [P.number, P.number]],
([[a, b], [c, d]]) => `Matrix [${a},${b}],[${c},${d}]`
)
.exhaustive();Combining Patterns
Complex Nested Structures
type Event =
| { type: 'click'; position: [x: number, y: number] }
| { type: 'keypress'; key: string }
| {
type: 'api';
response: { status: 'success' | 'error'; data: any };
};
match(event)
.with(
{ type: 'click', position: [P.number.gt(100), P._] },
({ position: [x, y] }) => `Click at x=${x} (> 100), y=${y}`
)
.with(
{ type: 'api', response: { status: 'success' } },
({ response }) => `API success: ${response.data}`
)
.otherwise(() => 'Other event');State Machine Example
type State =
| { status: 'idle' }
| { status: 'loading'; progress: number }
| { status: 'success'; data: { items: any[] } }
| { status: 'error'; error: { code: number; message: string } };
const render = (state: State) =>
match(state)
.with({ status: 'idle' }, () => 'Not started')
.with(
{ status: 'loading', progress: P.number.lt(50) },
({ progress }) => `Loading: ${progress}%`
)
.with(
{ status: 'loading', progress: P.number.gte(50) },
({ progress }) => `Almost done: ${progress}%`
)
.with(
{ status: 'success', data: { items: [] } },
() => 'No items found'
)
.with(
{ status: 'success', data: { items: P.array() } },
({ data }) => `Found ${data.items.length} items`
)
.with(
{ status: 'error', error: { code: 404 } },
() => 'Not found'
)
.with(
{ status: 'error' },
({ error }) => `Error ${error.code}: ${error.message}`
)
.exhaustive();Type Narrowing
TS-Pattern automatically narrows types based on your patterns:
type Shape =
| { kind: 'circle'; radius: number }
| { kind: 'rectangle'; width: number; height: number }
| { kind: 'triangle'; base: number; height: number };
const getArea = (shape: Shape) =>
match(shape)
.with({ kind: 'circle' }, ({ radius }) => {
// radius is available and typed as number
return Math.PI * radius ** 2;
})
.with({ kind: 'rectangle' }, ({ width, height }) => {
// width and height are available
return width * height;
})
.with({ kind: 'triangle' }, ({ base, height }) => {
// base and height are available
return (base * height) / 2;
})
.exhaustive();Best Practices
Use Discriminated Unions
// ✅ Good: Discriminated union with 'type' field
type Message =
| { type: 'text'; content: string }
| { type: 'image'; url: string };
match(message)
.with({ type: 'text' }, ...)
.with({ type: 'image' }, ...)
.exhaustive();Partial Matching for Flexibility
// ✅ Good: Match only what matters
match(user)
.with({ role: 'admin' }, ...) // Other fields ignored
.otherwise(...);
// ❌ Overly specific
match(user)
.with({ id: P.number, name: P.string, email: P.string, role: 'admin' }, ...)
.otherwise(...);