Type Assertions
Sometimes you know more about a value than TypeScript can work out. A type assertion — written with as — tells the compiler to treat a value as a particular type. It's powerful and occasionally necessary, but it's also a way to tell the compiler something false.
The as keyword
The classic example is the DOM. document.getElementById can return any kind of element, so its return type is the general HTMLElement | null. If you know it's an input, you can say so:
const field = document.getElementById("email") as HTMLInputElement; console.log(field.value);
Without the assertion, field.value would be an error, because a generic HTMLElement has no value property. With it, you get full autocomplete for inputs.
<div>, field.value will silently be undefined.Assertions don't convert values
const input: unknown = "42"; const wrong = input as number; const right = Number(input); console.log(typeof wrong, typeof right);
string number
TypeScript accepted input as number, but the value is still the string "42". To actually change a value's type, use a real conversion such as Number(), String() or parseInt().
Assertions must be plausible
TypeScript rejects assertions between types that can't possibly overlap:
const count = 5; const text = count as string;
error TS2352: Conversion of type 'number' to type 'string' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first.
As the message hints, you can force it with count as unknown as string — a "double assertion". If you find yourself writing one, it's almost always a sign the types are wrong somewhere upstream.
The angle-bracket form
An older syntax, <HTMLInputElement>field, does the same thing as field as HTMLInputElement. It can't be used in .tsx files because it clashes with JSX, so as is the standard choice today.
as const
as const is a special assertion that makes TypeScript infer the narrowest possible type: literal values instead of string/number, readonly properties, and readonly tuples instead of arrays:
const ROLES = ["admin", "editor", "viewer"] as const; type Role = (typeof ROLES)[number]; function canEdit(role: Role) { return role !== "viewer"; } console.log(ROLES.map(r => `${r}: ${canEdit(r)}`).join(", "));
admin: true, editor: true, viewer: false
Here ROLES is readonly ["admin", "editor", "viewer"], and (typeof ROLES)[number] extracts the union "admin" | "editor" | "viewer" (lesson 24 explains this syntax). You get a runtime list and a matching type from one source of truth — a popular alternative to enums.
satisfies: check without changing the type
A type annotation replaces the inferred type with the one you wrote. Sometimes that loses useful detail:
type Colors = Record<string, string | number[]>; const palette: Colors = { primary: "#d6336c", accent: [255, 200, 0], }; palette.primary.toUpperCase();
error TS2339: Property 'toUpperCase' does not exist on type 'string | number[]'.
The satisfies operator checks that a value matches a type but keeps the more specific inferred type:
type Colors = Record<string, string | number[]>; const palette = { primary: "#d6336c", accent: [255, 200, 0], } satisfies Colors; console.log(palette.primary.toUpperCase()); console.log(palette.accent.length);
#D6336C 3
The object is still validated against Colors (a typo or wrong value type would be an error), but TypeScript remembers that primary is a string and accent is an array.
Assertion vs annotation vs satisfies
| Syntax | Checks the value? | Resulting type |
|---|---|---|
const x: T = value | Yes — must be assignable to T | T |
const x = value as T | Barely — only rejects impossible cases | T |
const x = value satisfies T | Yes — must be assignable to T | The inferred type of value |
Prefer annotations and satisfies; reach for as only when you genuinely know something the compiler can't.