TSworkingts-evergreen

The satisfies operator

Check the type, keep the value.

satisfies validates a value against a wider type without widening the value β€” you get the constraint check AND the narrow inferred type, which a plain type annotation throws away.

The problem satisfies solves

A type annotation (const x: T = …) does two jobs: it checks the value against T, and it changes the static type of x to T. That second job is the catch β€” it widens the value, discarding the precise literal types the compiler had inferred. satisfies does only the first job. It checks the value against T, then leaves the variable with its original narrow inferred type.

satisfies vs annotation

With const config: Record<string, string | number> = … the compiler forgets which keys exist and that port is a number β€” every access is typed as string | number. Swap the : for value satisfies Record<…> and you still get the typo-and-shape check, but config.port stays a number and the key set stays known.

Preserving literal types

satisfies shines with object literals of union values β€” a palette, a route table, a config map. The compiler verifies every entry conforms, while each property keeps its literal type so you can branch on it, index into it, or feed it to a stricter API downstream without a cast.

Pairs well with as const

as const freezes a value into its deepest literal form but performs no validation β€” a typo'd hex string is still a valid string. Put satisfies after it (… as const satisfies Palette) to get both: the readonly literal types from as const and a compile-time shape check from satisfies.

Try it 5 examples

satisfies vs a plain annotation

TSintro
Press Run to execute

Output localhost 8081 proves checked.port stayed a number after satisfies β€” port + 1 arithmetic type-checks, whereas the widened annotated.port (string | number) would have errored.