Override the compiler when you know more than it does — as Type, the non-null assertion, as const for literal types, and satisfies for validation without widening.
Why: sometimes you know more than the compiler — a DOM lookup, a parsed JSON payload. Assertions override the inferred type. Note: they are a promise, not a check — nothing is verified at runtime, so a wrong assertion becomes a runtime crash.
// as Type — you guarantee what something is
type Product = { id: number; title: string };
const raw = '{"id": 1, "title": "TS"}';
const product = JSON.parse(raw) as Product; // JSON.parse returns any
console.log(product.title.toUpperCase());
// Classic DOM use — querySelector only knows "Element | null"
const el = document.querySelector('#app') as HTMLDivElement;
el.style.padding = '1rem';
// Non-null assertion (!) — "trust me, this is not null/undefined"
function firstWord(s?: string) {
return s!.split(' ')[0]; // crashes if s really is undefined
}
// as any — last-resort escape hatch; checking is OFF from here on
const win = window as any;
win.myGlobalThing = 123;
console.log(firstWord('hello world'));Why: as const freezes a value at the type level — every property becomes a readonly literal type instead of widening to string/number. It is the standard way to derive a type from a fixed list.
// Without as const, literals widen to general types
const config1 = { env: 'prod', retries: 3 };
// type: { env: string; retries: number }
// With as const — readonly literal types
const config2 = { env: 'prod', retries: 3 } as const;
// type: { readonly env: 'prod'; readonly retries: 3 }
// config2.retries = 5; // Error: read-only
// The killer use: a fixed list that is also a type
const ROLES = ['admin', 'editor', 'viewer'] as const;
type Role = (typeof ROLES)[number]; // 'admin' | 'editor' | 'viewer'
function setRole(role: Role) {
console.log('role set to', role);
}
setRole('admin'); // ok
// setRole('hacker'); // Error: not assignable to Role
console.log(config2.env, ROLES.length);Why: a plain annotation widens your value to the annotated type, losing the specific keys and literals. satisfies checks the value against a type but keeps the precise inferred type — validation without information loss.
type RouteConfig = Record<string, { path: string; auth?: boolean }>;
// Plain annotation: the keys collapse to "string"
const routes1: RouteConfig = {
home: { path: '/' },
admin: { path: '/admin', auth: true },
};
// routes1.admin — compiler no longer knows 'admin' exists
// satisfies: shape is checked, precise type is kept
const routes2 = {
home: { path: '/' },
admin: { path: '/admin', auth: true },
} satisfies RouteConfig;
routes2.admin.auth; // ok — 'admin' is a known key
// routes2.missing; // Error: property does not exist
// Typos in the shape are still caught:
// const bad = { home: { paht: '/' } } satisfies RouteConfig; // Error
console.log(routes2.home.path);