TypeScript course menu
TypeScript as, as const, satisfies and the Non-null Assertion (!)
Understand TypeScript's assertion tools: type assertions with as and their risks, double assertions, as const for literal and readonly types, the satisfies operator for checking without widening, the non-null assertion operator !, and when to use each.
On this page
Sometimes you know more about a value than TypeScript does, or you want TypeScript to infer a type in a particular way. TypeScript has four tools for this. They look similar but behave very differently, especially in how much safety they keep.
as: type assertions#
value as Type tells the compiler to treat a value as a specific type:
const raw: unknown = JSON.parse('{"name":"Asha","age":21}');
const user = raw as { name: string; age: number };
console.log(user.name, user.age + 1);Asha 22An assertion is not a conversion or a check. Nothing happens at runtime; it only changes what the compiler believes. If you are wrong, TypeScript cannot help:
const raw: unknown = JSON.parse('{"title":"Not a user"}');
const user = raw as { name: string };
console.log(user.name); // compiles fine, but the property is missing
console.log(typeof user.name);undefined
undefinedTypeScript does reject assertions between clearly unrelated types:
const count = 42;
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.The message mentions the "double assertion" escape hatch, value as unknown as Type, which bypasses even this check. Seeing it in code is a warning sign.
When as is reasonable: when you genuinely know the type, for example document.getElementById("email") as HTMLInputElement when the HTML is under your control (TypeScript with the DOM), or Object.keys(obj) as Array<keyof typeof obj>. For outside data, prefer a type guard that actually checks.
as const#
as const is different: it is always safe. It asks TypeScript to infer the narrowest types, with literal values and readonly everywhere:
const config = {
env: "production",
ports: [80, 443],
} as const;
// { readonly env: "production"; readonly ports: readonly [80, 443] }
type Env = typeof config.env; // "production"
const e: Env = "production";
console.log(e, config.ports[1]);production 443It is covered with examples in Literal Types.
satisfies#
satisfies checks that a value matches a type, without changing the type TypeScript infers for it. This combines the safety of an annotation with the precision of inference:
type Colour = string | [number, number, number];
type Palette = Record<"primary" | "accent", Colour>;
const withAnnotation: Palette = {
primary: "#2f5fe0",
accent: [232, 89, 12],
};
const withSatisfies = {
primary: "#2f5fe0",
accent: [232, 89, 12],
} satisfies Palette;
// withAnnotation.primary is Colour (string or tuple), so string methods need a check.
// withSatisfies.primary is known to be a string:
console.log(withSatisfies.primary.toUpperCase());
console.log(withSatisfies.accent.join(", "));#2F5FE0
232, 89, 12With an annotation (: Palette), every property has the general type Colour. With satisfies, TypeScript verifies the object is a valid Palette (all keys present, no typos, correct value types) but remembers that primary is a string and accent is a tuple.
Mistakes are still caught:
type Palette = Record<"primary" | "accent", string>;
const theme = {
primary: "#2f5fe0",
acent: "#e8590c",
} satisfies Palette;error TS2561: Object literal may only specify known properties, but 'acent' does not exist in type 'Palette'. Did you mean to write 'accent'?The misspelled key is caught, with a suggestion, just as with an annotation.
satisfies and as const combine well: { ... } as const satisfies Config gives literal, readonly types that are also checked against Config.
! : the non-null assertion#
A ! after an expression removes null and undefined from its type:
const scores = new Map([["asha", 92]]);
const ashaScore = scores.get("asha")!; // number instead of number | undefined
console.log(ashaScore + 1);93Like as, it has no runtime effect. Prefer a real check or a default (?? 0), and use ! only where the value is guaranteed to exist (null and undefined).
Which to use?#
Practice#
Practice exercise
Define type Route = { path: string; auth: boolean } and a routes object with home and dashboard entries. Use satisfies Record<string, Route> so that the object is checked, but routes.dashboard.path still has its literal type when combined with as const. Print the dashboard path and whether it needs auth.
Show solution
type Route = { path: string; auth: boolean };
const routes = {
home: { path: "/", auth: false },
dashboard: { path: "/dashboard", auth: true },
} as const satisfies Record<string, Route>;
type DashboardPath = typeof routes.dashboard.path; // "/dashboard"
const p: DashboardPath = routes.dashboard.path;
console.log(p, routes.dashboard.auth);/dashboard trueKey takeaway#
Finished this lesson?