Skip to Content
Api ReferenceAdditional Methods When Returntype Narrow Run

Last Updated: 3/18/2026


Additional Methods

Beyond the core .with(), .exhaustive(), and .otherwise() methods, TS-Pattern provides additional methods for specific use cases.

.when()

The .when() method adds a case that matches based on a predicate function, without requiring a pattern.

Signature

function when( predicate: (value: TInput) => unknown, handler: (value: TInput) => TOutput ): Match<TInput, TOutput>;

Basic Usage

import { match } from 'ts-pattern'; type Input = { score: number }; const output = match<Input>({ score: 85 }) .when( (input) => input.score >= 90, () => 'A - Excellent' ) .when( (input) => input.score >= 80, () => 'B - Good' ) .when( (input) => input.score >= 70, () => 'C - Average' ) .otherwise(() => 'F - Fail'); console.log(output); // => 'B - Good'

When vs. P.when

  • .when(): Top-level method, matches entire input
  • P.when(): Pattern function, can be nested in object/array patterns
// Using .when() - matches entire input match(input) .when((x) => x > 10, () => 'Greater than 10') .exhaustive(); // Using P.when() - matches nested property match(input) .with({ value: P.when((x) => x > 10) }, () => 'Value > 10') .exhaustive();

Combining .when() and .with()

match(data) .with({ type: 'user' }, () => 'User type') .when((d) => d.priority > 5, () => 'High priority') .when((d) => d.items.length === 0, () => 'Empty items') .otherwise(() => 'Default');

.returnType<T>()

Explicitly specify the return type of your match expression without setting the input type.

Signature

function returnType<TOutputOverride>(): Match<TInput, TOutputOverride>;

Basic Usage

match({ isAdmin, plan }) .returnType<number>() // All branches must return number .with({ isAdmin: true }, () => 100) .with({ plan: 'premium' }, () => 50) .with({ plan: 'free' }, () => 'Oops!') // ❌ Type error: not a number .exhaustive();

When to Use

Before .returnType():

// Had to specify both input and output types match< { isAdmin: boolean; plan: 'free' | 'premium' }, // ⚠️ Input type number // Output type >({ isAdmin, plan }) .with({ isAdmin: true }, () => 100) .exhaustive();

With .returnType():

// Only specify output type, input is inferred match({ isAdmin, plan }) .returnType<number>() // ✅ Just the output type .with({ isAdmin: true }, () => 100) .exhaustive();

Complex Return Types

type Result = | { success: true; data: string } | { success: false; error: Error }; const process = (input: unknown) => match(input) .returnType<Result>() .with(P.string, (str) => ({ success: true, data: str })) .with(P.number, () => ({ success: true, data: 'number' })) .otherwise(() => ({ success: false, error: new Error('Invalid') }));

.narrow()

The .narrow() method deeply narrows the input type to exclude all values that have been previously handled.

Signature

function narrow(): Match<Narrowed<TInput>, TOutput>;

Basic Usage

type Input = { color: 'red' | 'blue'; size: 'small' | 'large' }; declare const input: Input; const result = match(input) .with({ color: 'red', size: 'small' }, () => 'Red small') .with({ color: 'blue', size: 'large' }, () => 'Blue large') .narrow() // 👈 Narrows remaining cases .otherwise((narrowedInput) => { // narrowedInput type: // | { color: 'red'; size: 'large' } // | { color: 'blue'; size: 'small' } return 'Other combination'; });

Narrowing Union Types

type Status = 'idle' | 'loading' | 'success' | 'error'; declare const status: Status; match(status) .with('idle', () => 'Not started') .with('loading', () => 'In progress') .narrow() .otherwise((remaining) => { // remaining: 'success' | 'error' return `Final state: ${remaining}`; });

Nested Property Narrowing

The real power of .narrow() is narrowing nested properties:

type Message = | { type: 'text'; content: string; priority: 'high' | 'low' } | { type: 'image'; url: string }; declare const message: Message; match(message) .with({ type: 'text', priority: 'high' }, () => 'High priority text') .narrow() .with({ type: 'text' }, (msg) => { // msg: { type: 'text'; content: string; priority: 'low' } // priority is narrowed to 'low' return `Low priority: ${msg.content}`; }) .with({ type: 'image' }, (msg) => `Image: ${msg.url}`) .exhaustive();

When to Use .narrow()

Without .narrow():

// TypeScript doesn't automatically narrow nested properties match(input) .with({ color: 'red', size: 'small' }, ...) .otherwise((input) => { // input still has full type: // { color: 'red' | 'blue'; size: 'small' | 'large' } // ⚠️ Not narrowed! });

With .narrow():

match(input) .with({ color: 'red', size: 'small' }, ...) .narrow() // 👈 .otherwise((input) => { // input is narrowed: // | { color: 'red'; size: 'large' } // | { color: 'blue'; size: 'small' } // | { color: 'blue'; size: 'large' } // ✅ Previous case excluded! });

.run()

Executes the pattern matching expression and returns the result, without exhaustiveness checking.

Signature

function run(): TOutput;

Basic Usage

const output = match(input) .with(pattern1, handler1) .with(pattern2, handler2) .run(); // ⚠️ May throw if no pattern matches

Comparison with .exhaustive()

Feature.exhaustive().run()
Compile-time checking✅ Yes❌ No
Throws if unmatched✅ Yes✅ Yes
Type safetyHighLower
Use caseProduction codeQuick prototypes

When to Use .run()

Use .exhaustive() (preferred):

// ✅ Safe: TypeScript ensures all cases are covered type Status = 'idle' | 'loading' | 'success'; match(status) .with('idle', () => 'Not started') .with('loading', () => 'In progress') .with('success', () => 'Done') .exhaustive(); // TypeScript checks all Status values are handled

Use .run() (acceptable for certain cases):

// When input type is very broad (unknown, any) const processUnknown = (input: unknown) => match(input) .with(P.string, (s) => s.toUpperCase()) .with(P.number, (n) => n * 2) .run(); // Exhaustiveness checking isn't practical here

Error Handling

Both .run() and .exhaustive() throw if no pattern matches:

try { match(input) .with({ type: 'a' }, () => 'A') .run(); // Throws if input.type !== 'a' } catch (error) { console.error('No pattern matched'); }

Combining Methods

These methods can be combined for powerful pattern matching:

type Data = { status: 'pending' | 'approved' | 'rejected'; priority: 'low' | 'medium' | 'high'; value: number; }; const process = (data: Data) => match(data) .returnType<string>() // Specify return type .with({ status: 'approved', priority: 'high' }, () => 'Fast track') .narrow() // Narrow remaining cases .when( (d) => d.value > 1000, // Custom predicate () => 'High value item' ) .with({ status: 'rejected' }, () => 'Rejected') .exhaustive(); // Ensure all cases covered

Best Practices

Prefer .exhaustive() over .run()

// ✅ Good: Exhaustiveness checking match(status) .with('idle', ...) .with('loading', ...) .exhaustive(); // ❌ Avoid in production match(status) .with('idle', ...) .run(); // May throw at runtime

Use .returnType() for Clear Contracts

// ✅ Good: Return type is explicit function getUserRole(user: User): 'admin' | 'user' | 'guest' { return match(user) .returnType<'admin' | 'user' | 'guest'>() .with({ role: 'admin' }, () => 'admin') .with({ role: 'user' }, () => 'user') .otherwise(() => 'guest'); }

Use .narrow() for Complex Unions

// ✅ Good: Narrow for precise types in .otherwise() match(complexUnion) .with(specificCase1, ...) .with(specificCase2, ...) .narrow() .otherwise((remaining) => { // remaining is precisely typed });