The is keyword in TypeScript turns a function into a custom type guard. Write value is Type as the return type, and when the function returns true, TypeScript treats the argument as that type inside the if block:
function isString(value: unknown): value is string {
return typeof value === "string";
}
Nothing here is hand-typed output: I compiled the snippets under TypeScript 7.0.2 and Node.js v22.22.2 with --strict and copied each console result.
You’ll see unions, unknown API data, filter(), a guard that lies, and asserts.
What does the is keyword do in TypeScript?
is only works in a function’s return type, as parameterName is Type. That’s a type predicate: the body returns a boolean, and the predicate says what true means.
Here a payment can be a card or PayPal. isCard() narrows both branches:
interface CardPayment { kind: "card"; last4: string; amount: number }
interface PayPalPayment { kind: "paypal"; email: string; amount: number }
type Payment = CardPayment | PayPalPayment;
function isCard(p: Payment): p is CardPayment {
return p.kind === "card";
}
const payments: Payment[] = [
{ kind: "card", last4: "4242", amount: 89.99 },
{ kind: "paypal", email: "mia@example.com", amount: 45.5 },
];
for (const p of payments) {
if (isCard(p)) {
console.log(`Card ending ${p.last4}: $${p.amount.toFixed(2)}`); // p is CardPayment
} else {
console.log(`PayPal ${p.email}: $${p.amount.toFixed(2)}`); // p is PayPalPayment
}
}
Output:
Card ending 4242: $89.99
PayPal mia@example.com: $45.50

if, p is CardPayment; in the else, TypeScript knows it’s PayPalPayment.An inline p.kind === "card" narrows too; a guard pays off when you reuse the check. My TypeScript type narrowing guide covers the built-in checks.
Why use a type predicate instead of a boolean return?
A boolean return type tells the compiler nothing about the argument. Compare:
function isStringBool(value: unknown): boolean {
return typeof value === "string";
}
function isString(value: unknown): value is string {
return typeof value === "string";
}
const input: unknown = "Dallas";
if (isString(input)) {
console.log(input.toUpperCase()); // OK: narrowed to string
}
if (isStringBool(input)) {
console.log(input.toUpperCase()); // error: still unknown
}
Output:
is_boolean.ts(15,15): error TS18046: 'input' is of type 'unknown'.

is string guard compiles; the boolean one leaves input as unknown.Same runtime check, different return type. That’s why is matters most with unknown, which you can’t touch until it’s narrowed, unlike the any type in TypeScript.
Use a type guard to check unknown API data
JSON from a fetch call has no real type at runtime. A guard that checks each property lets you trust it, and filter() takes it directly:
interface Order { id: number; city: string; total: number }
function isOrder(x: unknown): x is Order {
return (
typeof x === "object" && x !== null &&
typeof (x as Order).id === "number" &&
typeof (x as Order).city === "string" &&
typeof (x as Order).total === "number"
);
}
const body: unknown = JSON.parse(`[
{"id": 1, "city": "Seattle", "total": 120.4},
{"id": 2, "city": "Boston"},
{"id": 3, "city": "Phoenix", "total": 75}
]`);
if (Array.isArray(body)) {
const orders: Order[] = body.filter(isOrder);
console.log("valid orders:", orders.length, "of", body.length);
for (const o of orders) console.log(`#${o.id} ${o.city.padEnd(8)} $${o.total.toFixed(2)}`);
}
const cities = ["Miami", null, "Denver", undefined];
const clean = cities.filter((c) => c != null); // TS 5.5+ infers c is string
console.log(clean.map((c) => c.toUpperCase()));
Output:
valid orders: 2 of 3
#1 Seattle $120.40
#3 Phoenix $75.00
[ 'MIAMI', 'DENVER' ]

total is dropped, and filter(isOrder) returns Order[], not unknown[].Since TypeScript 5.5, c => c != null gets an inferred predicate, so clean is string[], a quick way of removing undefined values from an array. More interface checks are in how to check if an object matches an interface.
Can a type guard be wrong?
Yes. TypeScript trusts the predicate and never verifies that the body proves it. This guard checks only name but promises a full Customer:
interface Customer { name: string; address: { state: string } }
// Wrong check: only looks at name, but promises a full Customer
function isCustomer(x: unknown): x is Customer {
return typeof x === "object" && x !== null && "name" in x;
}
const row: unknown = { name: "Ava Reed" }; // no address
if (isCustomer(row)) {
try {
console.log(row.address.state); // compiles fine
} catch (err) {
console.log("Runtime:", (err as Error).name, "-", (err as Error).message);
}
}
Output:
Runtime: TypeError - Cannot read properties of undefined (reading 'state')

address.state crashes because the guard lied.So check every property the code after the guard relies on. It’s the same risk as a careless type assertion in TypeScript, just hidden inside a function.
is vs asserts: assertion functions
An asserts value is Type function narrows everything after the call, and throws when the check fails:
function assertIsNumber(value: unknown, label: string): asserts value is number {
if (typeof value !== "number" || Number.isNaN(value)) {
throw new TypeError(`${label} must be a number, got ${JSON.stringify(value)}`);
}
}
function applyTax(price: unknown): string {
assertIsNumber(price, "price");
return (price * 1.0825).toFixed(2); // price is number from here on
}
console.log("Austin total: $" + applyTax(49.99));
try {
applyTax("49.99");
} catch (err) {
console.log((err as Error).message);
}
Output:
Austin total: $54.11
price must be a number, got "49.99"
Reach for asserts when your input must be valid, and is when you handle both outcomes.
Type predicate syntax at a glance
| Form | Narrows | Use it for |
|---|---|---|
x is T | The if and else branches | Reusable checks, filter() |
asserts x is T | Everything after the call | Validating input, throwing on bad data |
this is T | The object the method is called on | Class methods like isFile() |
boolean | Nothing | Checks where the type doesn’t matter |
Once guards feel natural, these type-checking articles pair well with them:
- Check the type of a variable in TypeScript
- TypeScript satisfies vs as
- Check if a key exists in a TypeScript object
- TypeScript enums explained
Frequently asked questions
What is the is keyword in TypeScript?
It creates a type predicate. A function returning value is Type tells TypeScript that a true result means the argument is that type.
Can I use is outside a function return type?
No. is is only valid as a return type, such as x is string or asserts x is string.
Does TypeScript check that my type guard is correct?
No. It trusts the predicate. A guard that checks too little compiles fine and fails at runtime, so test every property you rely on.
What’s the difference between is and as?
as forces a type with no runtime check. An is guard runs real code first and narrows only when it returns true.
Do I still need is for filter() in TypeScript 5.5 and later?
Often not for simple checks like x != null, which get an inferred predicate. Complex object checks still need an explicit is guard.
Where is the is keyword documented?
See Using type predicates in the TypeScript Handbook.

Bijay Kumar is a 13-time Microsoft MVP with more than 18 years in software development, and the founder of Python Guides and TSinfo Technologies. He started out building .NET and SharePoint solutions at HP, TCS and KPIT before moving into Python, machine learning and AI, and he also builds web apps with TypeScript and React. He writes the tutorials here himself, and every example is run before publishing so you see the real output. More about Bijay · Microsoft MVP profile · LinkedIn