Question

TypeScript `is` Keyword: Type Predicates and Type Guards

typescriptkeyword

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:

  1. The function returns a boolean at runtime.
  2. 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 as User.

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.

Take Quiz

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:

  1. response is declared as unknown, so TypeScript does not allow response.name yet.
  2. isProduct(response) runs at runtime.
  3. It verifies that the value is an object, is not null, and has id and name properties.
  4. The check returns true for this value.
  5. Because isProduct returns value is Product, TypeScript narrows response to Product inside the if body.
  6. 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.id or order.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()));
Take Quiz

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

FeatureRuntime behaviorTypeScript narrowingBest use
typeof value === "string"Checks a primitive typeYes, in the current branchSimple primitive checks
value instanceof DateChecks an object's prototype chainYes, in the current branchClasses and built-in objects
"name" in valueChecks whether a property existsYes, with appropriate object checksObject shapes and union members
value is TypeAnnotation for a function that returns booleanYes, wherever the guard is usedReusable custom validation
as TypeDoes nothing at runtimeForces TypeScript to accept the typeOnly 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.

Take Quiz

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 Type is 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 !== null when testing objects.
  • Validate properties and their types, not only property existence.
  • A predicate validates; as Type only 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.

Take Quiz

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

Take Quiz

Keep learning

Related questions

View more