"When typeof, instanceof, and in aren't enough, write your own narrowing function."
The form of a type predicate
A type predicate is a function with the return type annotation arg is T. The signature looks like function isUser(x: unknown): x is User. The function's body returns a boolean as usual. The magic is in the return type: when the function returns true, the caller learns that arg is type T.
From the caller's perspective, calling the predicate is just like a typeof or instanceof check — the value gets narrowed inside the true branch.
What goes inside
The body of the predicate runs at runtime. It does whatever check is necessary to verify the type: property existence, value types, structural matching, even calling other predicates. The compiler trusts the predicate — if it returns true, the narrowing happens. (This means buggy predicates lie to the compiler; structure the body carefully.)
For unknown inputs from external sources (JSON, user input, third-party APIs), the predicate is your validation. Tools like Zod, Valibot, and io-ts wrap this pattern with parser-generators that produce type-safe predicates.
When to write your own
- Validating untyped data (parsed JSON, dynamic fetches).
- Checking object shape for unions that don't have a clean discriminator.
- Wrapping checks that are too complex for a single typeof/instanceof.
T, the type system is now lying. Write predicates with the same care as a JSON parser — they're the boundary between unsafe input and safe code.