Three ways to relate a value to a type, three different results:
- Annotation (
const x: T = expr): checksexpragainstT, then the variable's type isT. Specific information is lost. Properties typedstring | RGBstaystring | RGB. as T: asserts. The compiler only requires that the types sufficiently overlap, and then treats the value asT. It does not do an assignability check, so missing properties pass. Nothing is checked at runtime.expr satisfies T(TS 4.9): checksexpragainstTthe way an annotation does (including excess-property errors), and the expression keeps its own inferred type.
satisfies also uses T as a contextual type. That is why callback parameters get types (click(e) {} against { click: (e: MouseEvent) => number } has e: MouseEvent) and why array literals can become tuples when T expects one. If T expects a literal type, the literal is kept.
When to use it: configuration objects, route tables, theme tokens, lookup maps. You want typos and wrong value shapes caught, and you want config.green.toUpperCase() to work because green is known to be a string. Typical forms are {...} satisfies Record<Keys, Value> and [...] as const satisfies readonly X[].
When not to: when you want the variable to be the wider type (use an annotation, for example so later assignments of other members are allowed), or when the value comes from outside the compiler's knowledge (JSON.parse result: narrow or validate it, don't assert it).
satisfies is erased at compile time, like annotations and assertions. It needs TS 4.9 or later.