function isUser(x: unknown): x is User {
return typeof x === "object" && x !== null && "id" in x;
}
function assertUser(x: unknown): asserts x is User {
if (!isUser(x)) throw new Error("Not a user");
}
A type predicate returns a boolean, and you use it in an if. An assertion function returns nothing and throws if the check fails. Both carry the same risk: TypeScript trusts them without checking.
A type predicate is a function with the return type x is User. The caller uses it in a condition. In the true branch, TypeScript narrows x to User. In the false branch, it removes User from the type.
An assertion function has the return type asserts x is User. It tells TypeScript: "If this call returns, x is a User." So x is a User for the rest of the scope, with no if needed. It suits validation at the top of a function.
The risk is the same for both. TypeScript trusts the declared signature and does not check the function body. If your check is wrong or incomplete, the compiler still narrows, and you get bugs at runtime.
To stay safe:
- Keep guards small and test them.
- For external data, like API responses, use a schema validator.
- A type predicate returns a boolean and narrows inside
if/else. - An assertion function throws on failure and narrows the rest of the scope.
- The compiler trusts the signature, not the code inside the function.
- A wrong guard silently gives the type system false information.