Skip to Content
Not

Last Updated: 3/6/2026


P.not() - Inverse Patterns

P.not() matches everything except what the provided pattern matches. It’s the logical inverse of a pattern.

Basic Usage

import { match, P } from 'ts-pattern'; type Input = boolean | number; const toNumber = (input: Input) => match(input) .with(P.not(P.boolean), (n) => n) // n: number .with(true, () => 1) .with(false, () => 0) .exhaustive(); console.log(toNumber(2)); // => 2 console.log(toNumber(true)); // => 1

Inverting Literals

type Status = 'idle' | 'loading' | 'success' | 'error'; const isActive = (status: Status) => match(status) .with(P.not('idle'), () => true) .with('idle', () => false) .exhaustive(); console.log(isActive('loading')); // true console.log(isActive('idle')); // false

Inverting Wildcards

// Match anything except strings match(value) .with(P.not(P.string), (v) => `Not a string: ${v}`) .with(P.string, (s) => `String: ${s}`) .exhaustive(); // Match anything except nullish values match(value) .with(P.not(P.nullish), (v) => `Has value: ${v}`) .with(P.nullish, () => 'Null or undefined') .exhaustive();

Inverting Object Patterns

type User = { type: 'user'; name: string }; type Guest = { type: 'guest'; id: string }; type Admin = { type: 'admin'; permissions: string[] }; type Account = User | Guest | Admin; const getName = (account: Account) => match(account) .with({ type: P.not('guest') }, (a) => { // a: User | Admin (not Guest) return 'name' in a ? a.name : 'admin'; }) .with({ type: 'guest' }, (g) => `Guest ${g.id}`) .exhaustive();

Inverting Union Patterns

type Input = | { type: 'a'; value: string } | { type: 'b'; value: number } | { type: 'c'; value: boolean } | { type: 'd'; value: string[] }; match(input) .with( { type: P.not(P.union('a', 'b')) }, (x) => { // x: { type: 'c', value: boolean } | { type: 'd', value: string[] } return 'c or d'; } ) .otherwise(() => 'a or b');

Nested P.not()

type Response = { status: 'success' | 'error' | 'pending'; data?: string; }; match(response) .with( { status: P.not('pending'), data: P.not(P.nullish), }, (r) => { // r.status: 'success' | 'error' // r.data: string return `${r.status}: ${r.data}`; } ) .otherwise(() => 'Pending or no data');

Combining with P.select()

match(data) .with( { value: P.not(0).select() }, (value) => { // value: number (but not 0) return `Non-zero: ${value}`; } ) .otherwise(() => 'Zero');

Double Negation

While P.not(P.not(pattern)) is valid, it’s better to just use the original pattern:

// Avoid match(value) .with(P.not(P.not(P.string)), (s) => s) .otherwise(() => ''); // Better match(value) .with(P.string, (s) => s) .otherwise(() => '');

Use Cases

Excluding Specific Values

type Config = { mode: 'development' | 'production' | 'test'; debug: boolean; }; match(config) .with( { mode: P.not('production') }, (c) => { // c.mode: 'development' | 'test' return { ...c, debug: true }; } ) .otherwise((c) => c);

Error Handling

type Result<T> = | { success: true; data: T } | { success: false; error: string }; const handleResult = <T>(result: Result<T>) => match(result) .with( { success: P.not(false) }, (r) => { // r: { success: true, data: T } return r.data; } ) .otherwise((r) => { throw new Error(r.error); });

State Machines

type State = | { status: 'idle' } | { status: 'loading'; progress: number } | { status: 'success'; data: string } | { status: 'error'; error: Error }; const canRetry = (state: State) => match(state) .with( { status: P.not('loading') }, () => true ) .otherwise(() => false);

Type Narrowing

P.not() provides precise type narrowing:

type Animal = | { type: 'dog'; bark: () => void } | { type: 'cat'; meow: () => void } | { type: 'bird'; chirp: () => void }; const makeSound = (animal: Animal) => match(animal) .with( { type: P.not('dog') }, (a) => { // a: { type: 'cat', meow: () => void } // | { type: 'bird', chirp: () => void } if (a.type === 'cat') a.meow(); else a.chirp(); } ) .with({ type: 'dog' }, (d) => d.bark()) .exhaustive();

Best Practices

  1. Use for exclusions: When it’s easier to specify what to exclude
  2. Combine with unions: Exclude multiple values at once
  3. Avoid double negation: Keep patterns simple
  4. Consider readability: Sometimes positive patterns are clearer
// Good: Clear exclusion match(status) .with(P.not('idle'), handleActive) .with('idle', handleIdle) .exhaustive(); // Less clear: Could be more explicit match(status) .with(P.not(P.not(P.not('idle'))), handler) .exhaustive(); // Better alternative: Be explicit match(status) .with('loading', handleLoading) .with('success', handleSuccess) .with('error', handleError) .with('idle', handleIdle) .exhaustive();

Performance

P.not() has minimal performance overhead as it simply inverts the result of the inner pattern match. However, complex nested patterns can slow down matching:

// Fast P.not(P.string) // Slower (multiple checks) P.not(P.union( { type: 'a', nested: { deep: P.array(P.string) } }, { type: 'b', nested: { deep: P.array(P.number) } } ))