What are user-defined type guards in TypeScript?

TypeScript is an extension of JavaScript that incorporates the features of statically typed languages. This enables the use of advanced programming techniques in JavaScript code, resulting in early error detection and the creation of more robust and maintainable code.

One of the features provided by TypeScript is type guards, which enable developers to refine the type of a variable by doing runtime checks. Although TypeScript has built-in type guards such as typeof and instanceof, developers have the ability to design their own unique type guards, which are referred to as user-defined type guards.

Benefits of user-defined type guards

User-defined type guards have several benefits:

  • Enhanced type safety: Developers can bolster the type safety of their code by creating custom functions for type checking, therefore reducing the likelihood of runtime errors.

  • Enhanced readability: Type guards enhance code readability by providing descriptive names for type-checking functions, therefore making the code more self-explanatory.

  • Flexibility: User-defined type guards let developers design customized type-checking logic that is specifically tuned to their unique application needs, offering flexibility in handling complex type scenarios.

Usage of user-defined type guards

To construct a user-defined type guard in TypeScript, developers follow these steps:

  1. Define a function with a meaningful name that accepts a parameter of the target typeThe specific type that needs to be checked or validated within the context of a type guard function. or any type.

  2. Implement the type-checking logic within the function using runtime checks, such as typeof, instanceof, or custom validation conditions.

  3. Return true if the value meets the type constraint or false otherwise.

Let’s explore some practical examples of user-defined type guards in TypeScript.

Custom type guard for arrays

In the following code, we’ll explore how to create a custom type guard function named isArray to check whether a value is an array or not.

function isArray(value: any): value is any[] {
// Use Array.isArray to check if the value is an array
return Array.isArray(value);
}
const data: unknown = [1, 2, 3]; // Declare a variable of unknown type and initialize it with an array
if (isArray(data)) {
// Check if the data is an array using the isArray function
console.log("Data is an array:", data); //If true, log that the data is an array along with the array contents
} else {
console.log("Data is not an array."); // If false, log that the data is not an array
}

In this example, the isArray type guard checks if a value is an array using Array.isArray. We then use this type guard to narrow down the type of the data variable inside the if statement block.

Custom type guard for dates

In the following code, we’ll define a custom type guard function named isDate to determine whether a value is a Date object or not.

function isDate(value: any): value is Date {
// Check if the value is an instance of the Date class
return value instanceof Date;
}
const userInput: unknown = new Date(); // Declare a variable of unknown type and initialize it with a new Date object
if (isDate(userInput)) {
// Check if the userInput is a Date object using the isDate function
console.log("User input is a Date object:", userInput.toISOString()); // If true, log that the user input is a Date object and display its ISO string representation
} else {
console.log("User input is not a Date object."); // If false, log that the user input is not a Date object
}

In this example, the isDate type guard checks if a value is an instance of the Date class using the instanceof operator. We then apply this type guard to validate user input and perform date-related operations if the input is a Date object.

Custom type guard for objects with specific properties

Let’s explore a custom type guard function called isPerson. This function is designed to check whether an object adheres to a specific structure defined by the Person interface.

Before we can create our type guard, we need an interface that defines the structure we want to check against.

interface Person {
name: string;
age: number;
}
Person interface

Now, let’s move on to its implementation.

interface Person {
name: string;
age: number;
}
function isPerson(value: any): value is Person {
// Check if the value is an object and has both 'name' and 'age' properties
return (
typeof value === "object" &&
"name" in value &&
typeof value.name === "string" &&
typeof value.age === "number"
);
}
const obj: unknown = { name: "John", age: 31 }; // Declare a variable of unknown type and initialize it with an object
if (isPerson(obj)) {
// Check if the obj is a Person using the isPerson function
console.log("Object is a Person:", obj.name, obj.age); // If true, log that the object is a Person and display its name and age
} else {
console.log("Object is not a Person."); // If false, log that the object is not a Person
}

In this example, the isPerson function serves as a custom type guard to determine if a given value is an object that matches the Person interface. We use this type guard to narrow down the type of the obj variable and access its properties safely.

Conclusion

Type-annotatedType annotation refers to the process of explicitly specifying the data type of a variable, function parameter, or function return value in TypeScript code. user-defined type guards play a vital role in boosting type safety, readability, and code flexibility in TypeScript. With the utilization of user-created validation functions which are used to verify the type of data at the runtime, developers can construct more powerful and maintainable systems. The practical examples and clarity about the advantages allow developers to manage TypeScript projects properly by employing user-defined type guards, which result in desired code quality and reliability.

Free Resources

Copyright ©2025 Educative, Inc. All rights reserved