satisfies
satisfies checks that a value's inferred type is assignable to a target type without replacing the value's own, more precise type. A non-conforming value is a compile error at the satisfies, not a silent coercion, and it inserts no runtime code. The conformance check is enforced today; literal-preservation across a member read is not.
satisfies checks that a value fits a type without changing the value's own, more precise type. You get the guarantee that the value conforms, and you keep the exact type the checker inferred for later use.
const palette = { primary: [0, 0, 0] } satisfies Record<string, [number, number, number]>
What it does
e satisfies T asks one question: is the inferred type of e assignable to T? If yes, the expression passes. If no, it is a compile error. Unlike a type annotation, satisfies does not replace the expression's own type with T — it checks the relationship and steps out of the way.
const config = { mode: "dark", level: "high" } satisfies Record<string, string>
// checked: every value conforms to the target, and the binding is accepted
The intent is that the expression keeps its inferred, more precise type so that keyof and indexed access still see the concrete keys and values. Be aware of a current gap: reading a member back out widens its type. In today's checker config.mode comes out typed string, not the literal "dark", so you cannot rely on member access to hand back the narrow literal type — see Limitations.
Non-conforming values are rejected
If the value does not fit the target, you get a compile error at the satisfies, not a silent coercion.
const config = { mode: "dark", level: "high" } satisfies Record<string, number>
// error: level is a string, not assignable to number
Why it works this way
satisfies only ever restricts. It inserts no runtime code and it never loosens a type. It checks a relationship that already has to hold and then steps out of the way, leaving the value exactly as it was. Because it can only reject or pass, and never manufactures a wider type, it is safe by construction. The looseness knob only turns as far as unknown: the loosest target you can write is unknown, never a catch-all that stops the checker reasoning. A satisfies unknown is a trivial check that still preserves the value's precise type, and it does not narrow a value entering your program from outside it.
Limitations
- It checks, it does not convert. If you want the value to have the target type, use an annotation.
satisfiesdeliberately keeps the inferred type instead. - Conformance is enforced; literal-preservation is not. The conformance half is real today: a value that does not fit the target is rejected at the
satisfieswith a compile error. The no-widening half does not hold for member access — a literal element type read back throughexpr.memberwidens to its base (e.g."/"becomesstring), so you cannot lean onsatisfiesto preserve narrow literal types across a member read yet.