A type guard is a runtime check that TypeScript understands, so it narrows the type inside the checked branch. typeof, instanceof and in are built-in guards; for anything else, you write a function whose return type is a type predicate, value is Type.
isUser returns a plain boolean at run time. The value is User return type tells the compiler what a true result proves, and every if (isUser(x)) then narrows x to User.
Built-in Type Guards
These checks narrow without any helper function:
| Guard | Example | Use it for |
|---|---|---|
typeof | typeof x === "number" | primitives and functions |
instanceof | x instanceof Date | class instances |
in | "email" in x | object unions, properties of unknown objects |
Array.isArray | Array.isArray(x) | arrays |
| Equality | x === null, x.kind === "circle" | null/undefined, literal tags |
| Truthiness | if (x) | removing null and undefined |
They all run as ordinary JavaScript. What TypeScript adds is the narrowing: it reads the check and adjusts the type in each branch. The full list of forms is on the type narrowing page. A custom guard is for checks that do not fit in one expression, or that you want to reuse.
Writing a Type Predicate
A type predicate has the form parameterName is Type and replaces boolean as the return type. The narrowing works in both directions: true narrows to Type, and false removes Type from a union.
Passing a guard to filter gives a correctly typed array. Since TypeScript 5.5 the compiler also infers a predicate from simple arrow functions, so pets.filter((p) => p.kind === "cat") returns Cat[] without a named guard.
The predicate's type must fit the parameter's type: function f(x: string): x is number is error TS2677, A type predicate's type must be assignable to its parameter's type.
The Compiler Trusts Your Guard
TypeScript checks that a guard returns a boolean. It does not check that the boolean is right. A guard that returns true for the wrong values makes the types lie, and the program fails at run time with no compile error.
data.price.toFixed(2) throws TypeError: Cannot read properties of undefined (reading 'toFixed') at run time. The compiler accepted data.price as a number because the guard said so. Check every property the rest of the code relies on, and keep guards small, tested and close to the type they describe.
Checking If an Object Is of a Type
This is the question behind most custom guards: data arrives as unknown (from JSON.parse, fetch, localStorage, a message) and you need to know whether it matches your interface. The recipe:
typeof value === "object" && value !== null(an object, notnull)."prop" in valuefor each required property. Onunknown,inadds the property to the type asunknown.typeof value.prop === "..."(or a nested guard) for each property's type.Array.isArray(value.items) && value.items.every(isItem)for arrays.
For large or deeply nested shapes, writing these by hand gets tedious. Schema libraries such as Zod or Valibot let you describe the shape once and give you both the runtime check and the TypeScript type.
Assertion Functions: asserts value is Type
An assertion function throws if the check fails and returns normally otherwise. Its return type is asserts value is Type (or asserts condition), and everything after the call is narrowed, with no if needed.
One rule trips people up: an assertion function must be called through a name with an explicit type. A const arrow function without an annotation, const check = (v: unknown): asserts v is string => {...}, gives error TS2775 at the call site, Assertions require every name in the call target to be declared with an explicit type annotation. Use a function declaration, or annotate the constant with a function type.
Guards vs Assertions vs Casts
| Tool | Runtime check? | Narrows | On failure |
|---|---|---|---|
Built-in guard (typeof, in...) | Yes | inside the branch | takes the other branch |
value is T function | Yes (your code) | inside the branch | takes the other branch |
asserts value is T function | Yes (your code) | after the call | throws |
value as T | No | the expression | nothing: the wrong type spreads |
A type assertion (as) changes the type without checking anything. At a boundary where data comes from outside, a guard or an assertion function is the safe version of the same idea.
this-Based Guards in Classes
A method can narrow the object it is called on with this is Type. It is handy in class hierarchies:
class FileNode {
constructor(public name: string) {}
isDirectory(): this is DirectoryNode {
return this instanceof DirectoryNode;
}
}
class DirectoryNode extends FileNode {
children: FileNode[] = [];
}
function count(node: FileNode): number {
return node.isDirectory() ? node.children.length : 0; // node: DirectoryNode in the true branch
}
Frequently Asked Questions
What is a type guard in TypeScript?
Any runtime check that TypeScript uses to narrow a type: typeof x === "string", x instanceof Date, "id" in x, Array.isArray(x), or a call to a function whose return type is a type predicate such as x is User. Inside the checked branch the variable has the narrower type.
How do I check if an object is of a type in TypeScript?
Types do not exist at run time, so you check the properties: write a function isUser(value: unknown): value is User that tests typeof value === "object", value !== null, and each required property with in and typeof. After if (isUser(x)), x is typed User. For classes, x instanceof MyClass is enough.
What does "value is Type" mean in TypeScript?
It is a type predicate, used as a function's return type. The function still returns a boolean at run time, but when it returns true TypeScript narrows the argument to Type at the call site, and when it returns false it narrows it to the other members of the union. The compiler does not verify the function body, so the check must be correct.
What is the difference between a type guard and an assertion function?
A type guard (x is T) returns a boolean and narrows inside an if. An assertion function (asserts x is T) returns nothing and throws when the check fails, so everything after the call is narrowed without an if. Use guards for branching and assertions for "this must hold, otherwise stop".
Can I check if an object implements an interface in TypeScript?
Not directly: interfaces are erased and instanceof does not accept them. Write a type guard that checks the interface's properties, or add a literal tag property (kind: "user") and compare it. Schema libraries such as Zod generate both the check and the type from one definition.