Type Guards in TypeScript
Type guards refine types at runtime checks so the compiler knows which branch handles which type.
Built-in guards
function len(x: string | string[]) {
if (typeof x === "string") return x.length; // string
return x.length; // string[]
}
function handle(el: HTMLElement | null) {
if (el instanceof HTMLDivElement) {
el.align = "center"; // HTMLDivElement
}
}
function hasId(x: unknown) {
if (typeof x === "object" && x !== null && "id" in x) {
// x: object with id
}
}
typeoffor primitivesinstanceoffor classes/DOMinfor property presence
User-defined type predicates
interface Ok<T> { ok: true; value: T }
interface Err { ok: false; error: string }
function isOk<T>(r: Ok<T> | Err): r is Ok<T> {
return (r as any).ok === true;
}
const r: Ok<number> | Err = Math.random() > 0.5 ? { ok: true, value: 1 } : { ok: false, error: "x" };
if (isOk(r)) console.log(r.value); // r: Ok<number>
Discriminated unions
type Shape =
| { kind: "circle"; r: number }
| { kind: "rect"; w: number; h: number };
function area(s: Shape) {
switch (s.kind) {
case "circle": return Math.PI * s.r ** 2;
case "rect": return s.w * s.h;
default: {
const _x: never = s; // exhaustive
return _x;
}
}
}
Assertion functions
Signal to the compiler that a condition must hold.
function assertHas<K extends PropertyKey>(key: K, x: unknown): asserts x is Record<K, unknown> {
if (!(typeof x === "object" && x !== null && key in x)) {
throw new Error("missing key");
}
}
Tips
- Prefer user-defined predicates for reusable guards
- Make unions discriminated with a literal
kindtag - Use assertion functions for invariants that should stop execution when false