All three are assertions: you tell the compiler something it cannot verify, and it believes you. They are removed from the emitted JavaScript. Nothing is checked at runtime, so a wrong assertion becomes a runtime error somewhere else.
value as T(or<T>value, not allowed in.tsx): treatsvalueasT. TypeScript only allows it when the types sufficiently overlap, meaning one is assignable to the other, so"hello" as numberis rejected (TS2352). To bypass, go throughunknownorany:x as unknown as T, the double assertion. That deliberately gives up all checking.value!(non-null assertion): removesnullandundefinedfrom the type. It only changes the type, somaybe!.lengththrows ifmaybeisundefined.let x!: T/prop!: T(definite assignment assertion, TS 2.7): suppresses "used before being assigned" (TS2454) or, on a class field,strictPropertyInitialization(TS2564). The handbook calls it the dual of!: one postfixes declarations, the other expressions.
When they are safe:
asthat widens (dog as Animal) is always safe.asthat narrows is a claim. It is reasonable right after a check the compiler can't model, for example a DOM lookup in a page you control, or data you have already validated.!is safe when an invariant guarantees presence: a map entry just inserted, a ref set in a lifecycle hook, test code.!:is reasonable for fields initialized by a framework (dependency injection, a lifecycle method) after the constructor.
Prefer an alternative when one exists: a type guard or assertion function (the claim gets checked), ?./?? or an early throw instead of !, satisfies instead of as for object literals, and unknown plus narrowing for external data.