Skip to Content
Patterns GuideObjects Tuples And Literals

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); // => 12

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