The scope of type guards and assertion functions
- Published at
- Updated at
- Reading time
- 2min
I've just read Absorbing unknown Into the Type Realm. It's a good read, check it out.
However I also learned an important difference between TypeScript type guards and assertion functions. And to be fair, I didn't even know assertion functions were a thing in TypeScript.
Let's look at a standard type guard.
tsinterfaceUser {name : string;}// standard type guardfunctionisUser (value : unknown):value isUser {return typeofvalue === "object" &&value !== null && "name" invalue ;}// let's pretend JSON.parse is an unknown data sourceconstdata =JSON .parse ('{"name": "Stefan"}');if (isUser (data )) {data .name .toUpperCase (); // narrowed to User inside of condition scope}data ;
If you receive data from an unknown source (most likely via fetch or HTTP), TypeScript doesn't know the types. To be on the safe side, you should bring in a validation library like Zod but if you only care about the types, a classic type guard might do the trick.
When you check the types above you see that thanks to the isUser type guard, data is of type User inside the condition scope. Great! But if you leave the scope you'll see that data is again back at any. Booh!
Now check this out!
tsinterfaceUser {name : string;}// assertion functionfunctionassertIsUser (value : unknown): assertsvalue isUser {if (typeofvalue !== "object" ||value === null || !("name" invalue )) {throw newError ("Expected a User");}}// let's pretend JSON.parse is an unknown data sourceconstdata =JSON .parse ('{"name": "Stefan"}');assertIsUser (data ); // throws if not a Userdata .name .toUpperCase (); // narrowed to User after assertion
If you now change the type guard to be an assertion function with asserts value is User the type will be adjusted for the rest of the current scope. The contract is: if this function doesn't throw, apply the type to the current scope.
Good stuff!

Join 6.5k readers and learn something new every week with Web Weekly.
