is (type predicate)
Declares that a boolean function narrows one of its parameters to a more specific type.
function isFish(pet: Fish | Bird): pet is Fish- Added in TypeScript 1.6
Parameters
| Parameter | Type | Description |
|---|---|---|
$pet | Fish | Bird | The name on the left of `is`. It must be a parameter of this same function, referenced by name. |
$Fish | Type | The type on the right of `is`. It must be assignable to the parameter's declared type. |
Return value
pet is FishA boolean at runtime. To the checker it also asserts that when the result is true the named parameter has the predicate type, and when it is false it does not.
Errors
Nothing at runtime. tsc errors if the predicate type is not assignable to the parameter's declared type, or if the name on the left is not a parameter.
Changelog
- PHP 1.6User-defined type guard functions introduced: a return type of the form `x is T` narrows `x` inside an `if` block.
- PHP 5.5TypeScript infers a type predicate for a function with no explicit return type, a single return statement, no parameter mutation, and a boolean expression that refines the parameter, so `.filter(x => x !== undefined)` narrows on its own.
A type predicate turns an ordinary boolean function into a narrowing tool. Write param is Type in the return position and every if that calls the function narrows the argument you passed in, in both branches.
When to reach for it
- The check is more than one operator, or you use it in more than one place, so
typeoforininline stops being readable. - You need
Array.prototype.filterto change the element type. A plainbooleancallback returns the same array type you started with; a predicate callback returns the narrowed one. - You are validating unknown input at a boundary and want the rest of the file to see a real type.
What trips people up
The compiler does not verify the body. function isFish(p: Fish | Bird): p is Fish { return true; } compiles. A predicate is an assertion you are making, so the body is yours to get right.
The semantics are if and only if: returning false claims the value is not the type. That is why a truthiness check such as score => !!score does not qualify as a predicate in TypeScript 5.5's inference — a 0 would return false while still being a number.
The left side must be a parameter name. To narrow this, use this is T instead.
Example
When to use it
- Filtering a mixed array down to one member of the union with .filter(isFish)
- Validating a JSON payload once and treating it as a typed object afterwards
- Stripping null and undefined out of a mapped array so downstream code needs no optional chaining
Frequently asked questions
Why does my filter still return the wide union type?
Your callback returns plain boolean, so filter picks the non-narrowing overload. Annotate the callback's return as `(x): x is Narrow =>`, or upgrade to TypeScript 5.5 and write a single-return expression such as `x => x !== undefined`, which the compiler now infers a predicate for.
Does TypeScript check that my type guard is correct?
No. The body is unchecked — `return true` compiles. A predicate is an assertion you make to the compiler, so a wrong guard produces a runtime crash with no compile error.
What is the difference between a type predicate and an assertion function?
A predicate returns a boolean you branch on; an assertion function returns nothing and throws instead, narrowing the rest of the enclosing scope. Use `is` when you want an if/else, `asserts` when a failure should abort.
More examples
Inline predicate in a filter callback
Prints [ 'nemo' ]. Without `(pet): pet is Fish`, filter returns `(Fish | Bird)[]` and `f.name` would be the only property you could reach. Note that returning false for sharkey is a lie about its type — it is still a Fish — which is legal but is exactly the looseness predicates allow.
type Fish = { name: string; swim: () => string };
type Bird = { name: string; fly: () => string };
const zoo: (Fish | Bird)[] = [
{ name: "sharkey", swim: () => "swims" },
{ name: "nemo", swim: () => "swims" },
{ name: "tweety", fly: () => "flies" },
];
// The annotation goes on the parameter list of the arrow itself.
const friendly: Fish[] = zoo.filter((pet): pet is Fish => {
if (pet.name === "sharkey") return false;
return "swim" in pet;
});
console.log(friendly.map((f) => f.name));Validating unknown input at a boundary
Prints [ '[email protected]' ]. The `typeof value === "object" && value !== null` pair is required because typeof null is "object"; the `in` checks then narrow the remaining `object` type field by field (possible since TypeScript 4.9).
type User = { id: number; email: string };
function isUser(value: unknown): value is User {
return (
typeof value === "object" &&
value !== null &&
"id" in value &&
typeof value.id === "number" &&
"email" in value &&
typeof value.email === "string"
);
}
const payloads: unknown[] = [
{ id: 1, email: "[email protected]" },
{ id: "2", email: "[email protected]" },
null,
];
console.log(payloads.filter(isUser).map((u) => u.email));
Discussion