Type Predicate Function
Extracting type guards into helper functions breaks TypeScript's narrowing unless you explicitly tell it what the function is proving.
The problem
You have a complex check that you want to reuse. You move it into a helper function that returns a boolean.
type User = { name: string; role: string };
function isUser(obj: any) {
return obj && typeof obj.name === "string" && typeof obj.role === "string";
}
function process(data: unknown) {
if (isUser(data)) {
// Error: data is still 'unknown'.
// TypeScript knows isUser returned true, but doesn't know WHY.
// console.log(data.name);
}
}
The solution: arg is Type
A Type Predicate is a special return type in the format parameterName is Type. It tells the compiler: "If this function returns true, you can safely assume the parameter is this specific type."
// Note the return type: 'obj is User'
function isUser(obj: any): obj is User {
return obj && typeof obj.name === "string" && typeof obj.role === "string";
}
function process(data: unknown) {
if (isUser(data)) {
// Valid! data is narrowed to 'User'
console.log(data.name);
}
}
Filter arrays flawlessly
The most powerful use case for type predicates is filtering arrays. Array.filter(Boolean) does not remove null from the array's type. A type predicate fixes this.
const strings = ["a", null, "b", undefined];
// Returns 'string | null | undefined' array
const naiveFilter = strings.filter(item => item !== null);
// Helper predicate
function isString(item: string | null | undefined): item is string {
return item !== null && item !== undefined;
}
// Returns strictly 'string[]' array!
const safeFilter = strings.filter(isString);
Use type predicates (
arg is Type) when extracting type guard logic into reusable boolean functions so TypeScript's narrowing mechanisms can cross the function boundary.