BeCuriouzLearn something new every day
Assertion Functions: Narrowing That Throws
💡 An assertion function narrows a value for the rest of the scope by throwing when the check fails — declared with an asserts return type the compiler trusts on faith.
Recall from earlier cards that a user-defined type guard returns x is Foo, letting you narrow inside an if branch. An assertion function flips this around: instead of returning whether a value has a type, it throws if the value doesn't — and if it doesn't throw, TypeScript narrows the value for the rest of the current scope. The signature uses the asserts keyword.
function assert(cond: unknown, msg?: string): asserts cond {
if (!cond) throw new Error(msg ?? "Assertion failed");
}
function area(w: number | undefined) {
assert(w !== undefined, "width required");
return w * w; // w is now `number`, not `number | undefined`
}The magic is the return type asserts cond. It tells the compiler: "if this function returns normally, treat cond as true from here on." You can also assert a specific type with asserts x is T, the assertion cousin of a type guard:
function assertString(x: unknown): asserts x is string {
if (typeof x !== "string") throw new Error("not a string");
}
let val: unknown = getInput();
assertString(val);
val.toUpperCase(); // val is `string` below this lineUnlike an if-based guard that narrows only inside a block, an assertion narrows everything after the call in the same scope — no nesting needed. The catch: TypeScript trusts you completely. If your function body's logic doesn't actually match the asserts claim, the compiler won't warn you, and a bad value slips through as if it were valid.