A type assertion (as) tells the compiler "trust me, treat this value as this type" without any actual runtime check or conversion — it is not a cast in the C#/Java sense:
const input = document.getElementById("email") as HTMLInputElement;
input.value; // compiles — but only actually safe if the element really is an <input>
const value = "42" as unknown as number;
// forcing an assertion through `unknown` bypasses TypeScript's normal check that the
// two types must overlap — a strong signal this code deserves a second look
The non-null assertion operator (!) tells the compiler a value that's typed as possibly null/undefined definitely isn't, at this specific point:
function getElement(id: string): HTMLElement | null {
return document.getElementById(id);
}
const el = getElement("app")!; // asserts it's not null — no runtime check happens
el.textContent = "Hello"; // if getElement actually returned null, this throws at runtime
Both operators are compile-time-only promises with zero runtime effect — if the assertion is wrong, TypeScript doesn't catch it, and the mistake surfaces as an ordinary runtime error (often a confusing one, since the surrounding code was written as if the type were guaranteed). They're occasionally necessary — working with the DOM, or narrowing a case the compiler genuinely can't infer — but reaching for as/! to silence an error instead of fixing the underlying type mismatch is one of the most common ways real codebases quietly lose the safety TypeScript is supposed to provide.