BeCuriouzBeCuriouz

Learn something new every day

🔷 TypeScript
🚧

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.

ts
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:

ts
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 line

Unlike 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.