Question
What does is mean in a TypeScript function return type such as the following?
export function foo(arg: string): arg is MyType {
// implementation determines whether arg matches MyType
return /* boolean result */;
}
How does arg is MyType affect the type of arg when the function is used?
Short Answer
In this context, is is part of a type predicate: a special TypeScript return-type annotation used to create a custom type guard. The function still returns a JavaScript boolean at runtime, but TypeScript narrows a value's type when the result is true.
Concept
arg is MyType is not a normal JavaScript operator. It is TypeScript syntax called a type predicate.
A predicate has this shape:
parameterName is Type
It tells TypeScript two things:
- The function returns a
booleanat runtime. - If the result is
true, the specified parameter can safely be treated as the given type in that code branch.
For example:
type User = {
name: string;
email: string;
};
function isUser(value: unknown): value is User {
return (
typeof value === "object" &&
value !== null &&
"name" in value &&
"email" in value
);
}
The runtime checks are ordinary JavaScript. The value is User annotation gives TypeScript extra information for compile-time type checking.
This matters because data from APIs, JSON, form inputs, local storage, and third-party libraries is often unknown or a union of possible types. A type predicate lets you validate that data before using it as a more specific type.
Mental Model
Think of a value with type unknown as a sealed package with no label. A type guard is an inspection step.
- The function returns
false: the package did not pass inspection, so do not treat it as the requested type. - The function returns
true: the package passed the checks, so TypeScript lets you attach a temporary label such asUser.
The is annotation is TypeScript's promise: “When this function says true, this argument has this type.” TypeScript trusts that promise, so the function's checks must be accurate.
Syntax and Examples
The basic syntax is:
function isSomething(value: unknown): value is Something {
return /* a boolean expression */;
}
Here is a common primitive example:
function isString(value: unknown): value is string {
return typeof value === "string";
}
const input: unknown = "hello";
if (isString(input)) {
console.log(input.toUpperCase()); // input is string here
}
Inside the if block, TypeScript narrows input from unknown to string. Therefore, calling toUpperCase() is allowed.
Type predicates are especially useful with union types:
type ApiResult = string | number | null;
function isNonEmptyString(value: ApiResult): value is string {
return typeof value === "string" && value.length > 0;
}
const result: ApiResult = "ready";
if (isNonEmptyString(result)) {
console.log(result.length); // result is string
}
Step by Step Execution
Consider this example:
type Product = {
id: number;
name: string;
};
function isProduct(value: unknown): value is Product {
return (
typeof value === "object" &&
value !== null &&
"id" in value &&
"name" in value
);
}
const response: unknown = { id: 7, name: "Notebook" };
if (isProduct(response)) {
console.log(response.name.toUpperCase());
}
Step by step:
responseis declared asunknown, so TypeScript does not allowresponse.nameyet.isProduct(response)runs at runtime.- It verifies that the value is an object, is not
null, and hasidandnameproperties. - The check returns
truefor this value. - Because
isProductreturnsvalue is Product, TypeScript narrowsresponsetoProductinside theifbody. response.name.toUpperCase()is now accepted by the compiler.
Outside the if statement, response remains unknown; the narrowing applies only where TypeScript can prove the guard succeeded.
Real World Use Cases
Type predicates are useful whenever code receives data whose shape is not guaranteed.
- API responses: Verify JSON before reading fields such as
user.idororder.total. - Form processing: Check whether submitted values are strings, numbers, or valid objects.
- Event handling: Narrow an event target before accessing element-specific properties.
- Parsing configuration: Validate values read from environment variables, JSON files, or local storage.
- Mixed arrays: Keep only a particular kind of value from data containing multiple types.
For example, filter out non-string values:
function isString(value: unknown): value is string {
return typeof value === "string";
}
const values: unknown[] = ["red", 42, null, "blue"];
const names = values.filter(isString);
// names has type string[]
console.log(names.map(name => name.toUpperCase()));
Real Codebase Usage
In real projects, type predicates are usually small, focused validation helpers.
Guard clauses
Validate early and stop when data is invalid:
function isErrorWithMessage(value: unknown): value is { message: string } {
return (
typeof value === "object" &&
value !== null &&
"message" in value &&
typeof value.message === "string"
);
}
function showError(error: unknown): string {
if (!isErrorWithMessage(error)) {
return "An unknown error occurred.";
}
return error.message;
}
Discriminated unions
When possible, prefer a reliable tag field and check it directly:
type Result =
| { kind: "success"; data: string }
| { kind: "failure"; message: string };
function isSuccess(result: Result): result is Extract<Result, { kind: "success" }> {
return result.kind === "success";
}
Array filtering
Pass a predicate directly to filter so TypeScript infers a narrower output array:
const records: Array<string | undefined> = ["a", undefined, "b"];
const definedRecords = records.filter(
(record): record is string => record !== undefined
);
// definedRecords is string[]
Keep guards close to the data boundary: where untrusted data enters the application. For complex external data, a dedicated runtime validation library may be more maintainable than many handwritten checks.
Common Mistakes
Assuming is performs a runtime conversion
A type predicate does not turn a value into a new type. It only returns a boolean and informs the compiler about the true branch.
function isNumber(value: unknown): value is number {
return typeof value === "number";
}
This checks a value; it does not convert "12" into 12.
Writing an inaccurate guard
TypeScript trusts the annotation, even when the implementation does not truly check the claimed type.
// Broken: this only checks that value is an object.
function isUser(value: unknown): value is { name: string } {
return typeof value === "object" && value !== null;
}
This can cause a runtime failure:
const value: unknown = {};
if (isUser(value)) {
console.log(value.name.toUpperCase()); // crashes at runtime
}
Check every property your later code depends on:
function isUser(value: unknown): value is { name: string } {
return (
typeof value === "object" &&
value !== null &&
"name" in value &&
typeof value.name === "string"
);
}
Forgetting that typeof null is "object"
Always exclude null before using object checks:
const isObject = typeof value === "object" && value !== null;
Using a different parameter name in the predicate
The name before is must refer to a function parameter.
function isString(value: unknown): value is string {
return typeof value === "string";
}
Expecting narrowing after mutation
Narrowing may be lost if a value is reassigned or if control flow can no longer guarantee the check still applies. Avoid changing a narrowed value unexpectedly.
Comparisons
| Feature | Runtime behavior | TypeScript narrowing | Best use |
|---|---|---|---|
typeof value === "string" | Checks a primitive type | Yes, in the current branch | Simple primitive checks |
value instanceof Date | Checks an object's prototype chain | Yes, in the current branch | Classes and built-in objects |
"name" in value | Checks whether a property exists | Yes, with appropriate object checks | Object shapes and union members |
value is Type | Annotation for a function that returns boolean | Yes, wherever the guard is used | Reusable custom validation |
as Type | Does nothing at runtime | Forces TypeScript to accept the type | Only when the type is already known |
A type assertion is not a check:
const user = response as { name: string };
A type predicate is a check plus narrowing:
if (isUser(response)) {
console.log(response.name);
}
Prefer a predicate for data you do not control. Use as sparingly, because it can hide mistakes from the compiler.
Cheat Sheet
// General form
function isThing(value: unknown): value is Thing {
return true; // only when value really is Thing
}
// Primitive guard
function isString(value: unknown): value is string {
return typeof value === "string";
}
// Object guard
function isPoint(value: unknown): value is { x: number; y: number } {
return (
typeof value === "object" &&
value !== null &&
"x" in value &&
typeof value.x === "number" &&
"y" in value &&
typeof value.y === "number"
);
}
// Use it for narrowing
if (isString(input)) {
input.toUpperCase(); // input: string
}
// Use it with filter
const strings = values.filter(isString); // string[]
parameter is Typeis a type predicate.- The function's runtime result is still a boolean.
- The parameter name must be a parameter of that function.
- TypeScript trusts a predicate when it returns
true. - Check
value !== nullwhen testing objects. - Validate properties and their types, not only property existence.
- A predicate validates;
as Typeonly asserts.
FAQ
Is is a JavaScript keyword in this example?
No. arg is MyType is TypeScript-only type-predicate syntax. It is removed when TypeScript compiles to JavaScript.
Does a function returning arg is MyType return a MyType?
No. It returns a boolean. The annotation tells TypeScript that arg should be treated as MyType when that boolean is true.
Why not return boolean instead?
A return type of boolean tells TypeScript only that the result is true or false. A type predicate also connects true to a specific narrowed type.
Can a type predicate use unknown as its parameter type?
Yes. unknown is a common choice because it forces validation before values are used.
Can I use a type predicate with Array.filter()?
Yes. A correctly typed predicate lets filter produce a narrowed array type, such as converting unknown[] into string[].
Does checking that a property exists prove its type?
No. "name" in value only proves that the property exists. Also check its value, such as typeof value.name === "string", if your code needs a string.
Should I use a type predicate for every condition?
No. Use ordinary typeof, instanceof, or discriminated-union checks inline when they are simple. Create predicates when a check is reused or when naming the validation improves readability.
Mini Project
Description
Build a small API-response validator. It receives unknown data, recognizes valid task objects, filters invalid entries, and then safely displays task details. This mirrors a common boundary between untrusted JSON and typed application code.
Goal
Create and use a custom type predicate that converts an unknown[] response into a safe Task[] array.
Requirements
Requirement 1
Keep learning
Related questions
@Directive vs @Component in Angular: Differences, Use Cases, and When to Use Each
Learn the difference between @Directive and @Component in Angular, including use cases, examples, and when to choose each.
Accessing Input Value from EventTarget in TypeScript
Learn why EventTarget has no value property in TypeScript and safely read values from HTML input events in Angular applications.
Angular (change) vs (ngModelChange): What’s the Difference?
Learn the difference between Angular (change) and (ngModelChange), when each fires, and which one to use in forms and inputs.