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)); // => 1Inverting 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')); // falseInverting 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
- Use for exclusions: When it’s easier to specify what to exclude
- Combine with unions: Exclude multiple values at once
- Avoid double negation: Keep patterns simple
- 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) } }
))