Type Guards and Assertions
User-defined type guards, runtime validation for unknown data, assertion functions, and exhaustive never checks.
Built-in narrowing covers common checks. User-defined guards and assertions package those checks into reusable functions.
A type guard returns a predicate like value is string[]. When it returns true, TypeScript narrows the value in the caller.
function isStringArray(value: unknown): value is string[] {
return (
Array.isArray(value) && value.every((item) => typeof item === "string")
);
}
const raw: unknown = JSON.parse('["a", "b", "c"]');
if (isStringArray(raw)) {
console.log(raw.join(", "));
}An assertion function throws when the condition fails and narrows everything after the call.
function assertDefined<T>(
value: T | null | undefined,
msg: string,
): asserts value is T {
if (value == null) throw new Error(msg);
}
declare function loadUser(): { name: string } | null;
const maybeUser: { name: string } | null = loadUser();
assertDefined(maybeUser, "user required");
console.log(maybeUser.name);In production
Use guards for reusable validation and assertions for required boundary checks such as config loading. Both keep unsafe data close to the edge and let normal application code stay strongly typed.
Enjoyed this? Get more software craft delivered to your inbox.