#Type assertions with as
A type assertion tells the compiler "treat this expression as type T — I know more than you do here." It's written value as T (or the older <T>value, which doesn't work in .tsx files because it looks like JSX). The compiler then uses T for that expression instead of what it inferred.
There are real situations where you do know more than the checker. document.getElementById("email") can only promise HTMLElement | null; you wrote the HTML and know it's an <input>. JSON.parse returns any; you know which shape the endpoint sends. An assertion is how you pass that knowledge on.
The mental model: an assertion is a signed statement, not a check. The compiler doesn't verify it against reality — there's nothing at runtime to verify against, because types are erased. It only does a sanity check that the claim is plausible: the two types must overlap, meaning one of them is assignable to the other. If you're wrong, the compiler won't find out, and neither will you until something breaks at runtime.
const el = document.getElementById("email"); // const el: HTMLElement | null
const input = document.getElementById("email") as HTMLInputElement; // const input: HTMLInputElement
console.log(input.value);
const label = <HTMLLabelElement>document.querySelector("label"); // older syntax, same meaning
const n = "42" as number; // ❌ TS2352: Conversion of type 'string' to type 'number' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first.- Without an assertion,
getElementByIdreturnsHTMLElement | null.el.valuewouldn't compile: a plainHTMLElementhas novalue, and it might benull. as HTMLInputElementasks the checker to useHTMLInputElementinstead. It first checks the claim is plausible: isHTMLInputElementassignable toHTMLElement | null? Yes — it's a subtype. So the assertion is accepted, andinput.valueis astring.- Notice the assertion also quietly removed
null. If there's no#emailelement on the page,inputisnullat runtime andinput.valuethrows — the assertion covered that up. <HTMLLabelElement>…is the angle-bracket form. It's identical in.tsfiles and a syntax error in.tsx, so most code usesaseverywhere."42" as numberis rejected with TS2352:stringisn't assignable tonumberandnumberisn't assignable tostring, so the types don't "sufficiently overlap". This is the only protection an assertion gives you — it stops claims that are obviously impossible, not claims that are merely wrong.
The overlap rule works in both directions, which is what makes assertions useful and dangerous at the same time:
interface User {
id: number;
name: string;
}
const widened = "hello" as string; // up: a literal to its wider type
const narrowed = ({ id: 1, name: "Ana" } as object) as User; // down: a wider type to a narrower one
const incomplete = { id: 1 } as User; // compiles — but name is missing
const fromApi = JSON.parse('{"id":1}') as User; // any → anything is always allowed"hello" as stringgoes up the type hierarchy (literal"hello"tostring). That's always safe — it's the same as an annotation would allow.(… as object) as Usergoes down:objectis wider thanUser, andUseris assignable toobject, so the claim "thisobjectis actually aUser" is plausible and accepted.{ id: 1 } as Useris also a downcast:Useris assignable to{ id: number }(it hasidand more), so the types overlap and the compiler accepts it — even thoughnameis missing. Compare an annotation,const u: User = { id: 1 }, which would be an error (see Annotations vs assertions).JSON.parsereturnsany, andanyoverlaps with everything, soas Useris always accepted and never checked. The object at runtime has nonameat all.- So the rule is: an assertion may widen (safe) or narrow (unchecked). The compiler can't tell a correct narrowing from a wrong one.
When assertions are reasonable: DOM lookups of elements you control, narrowing something the checker can't follow (but prefer a type guard if you can), test doubles, and the inside of small, well-tested helpers. When to avoid them: anything that came from outside your program — API responses, localStorage, user input. For those, validate at runtime (see Validating runtime data).