Type Guard in TypeScript
Expert-led Courses: Transform Your Career – Enroll Now
One of the many features that make TypeScript powerful is its ability to use type guards. Type guards are a way to narrow down the type of a variable at runtime. This allows developers to write more robust code and be less prone to errors.
In this article, we will explore TypeScript type guards in detail. We will start by defining what type guards are and why they are important. We will then look at some examples of type guards and explore different ways to implement them.
What are TypeScript Type Guards?
TypeScript Type Guards are a way to narrow down the type of a variable at runtime. They allow developers to write more robust code and are less prone to errors. A type guard is a function that checks the type of a variable and returns a boolean value. If the value is true, the variable is of the expected type. If the value is false, the variable is not of the expected type.
Why are Type Guards Important?
TypeScript is designed to be a statically typed language. This means that the types of variables are checked at compile time rather than at runtime. However, there are still situations where we need to check the variable type at runtime. For example, when dealing with dynamic data or third-party libraries, we may only sometimes know the exact variable type. In these situations, type guards can be very useful. They allow us to narrow down the type of a variable at runtime and write code that is more robust and less prone to errors.
Example of Type Guards in Action
Let’s start with a simple example to demonstrate the use of type guards. Suppose we have a function that takes an argument of type string or number and returns the length of the argument. We want to ensure that the function only accepts type string or number arguments. We can use a type guard to accomplish this.
function DataFlair_getLength(input: string | number): number {
if (typeof input === 'string') {
return input.length;
} else if (typeof input === 'number') {
return input.toString().length;
}
}
console.log(DataFlair_getLength('hello')); // Output: 5
console.log(DataFlair_getLength(12345)); // Output: 5
console.log(DataFlair_getLength(true)); // Output: Error: Argument of type 'boolean' is not assignable to parameter of type 'string | number'.
In the above example, we define a function called DataFlair_getLength that takes an argument of type string or number. We use a type guard to check the type of the input parameter. If the input parameter is of type string, we return the length of the string. If the input parameter is of type number, we first convert it to a string and then return the length of the resulting string.
We then call the DataFlair_getLength function with two arguments, a string and a number. The function returns the length of each argument correctly. However, we get an error if we try to call the function with a boolean argument. This is because the boolean type is not assigned to the string or number type the function accepts.
Different Ways to Implement Type Guards in TypeScript
Type guards can be implemented in TypeScript in a variety of methods. We will explore some of the most common ways below.
1. typeof Type Guard
The typeof type guard checks the type of a variable using the typeof operator. The typeof operator returns a string that indicates the variable’s type. For example, typeof ‘hello’ returns ‘string’. We can use the typeof operator to check the type of a variable and return a boolean value.
function DataFlair_isString(input: unknown): input is string {
return typeof input === 'string';
}
if (DataFlair_isString('hello')) {
console.log('Input is a string');
} else {
console.log('Input is not a string');
}
In the above example, we define a function called DataFlair_isString that takes an argument of type unknown. The function uses the typeof operator to check if the input parameter is of type string. If the input parameter is of type string, the function returns true. Otherwise, it returns false.
We then use the DataFlair_isString function to check if a variable is of type string. We log a message to the console if the variable is of type string.
2. instanceof Type Guard
The instanceof type guard checks if a variable is an instance of a specific class or constructor function. This type guard is useful when dealing with object-oriented programming concepts such as inheritance.
class DataFlair_Person {
name: string;
age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
}
class Employee extends DataFlair_Person {
id: number;
constructor(name: string, age: number, id: number) {
super(name, age);
this.id = id;
}
}
function isEmployee(input: unknown): input is Employee {
return input instanceof Employee;
}
const person = new DataFlair_Person('John', 30);
const employee = new Employee('Jane', 25, 123);
console.log(isEmployee(person)); // Output: false
console.log(isEmployee(employee)); // Output: true
In the above example, we define two classes, DataFlair_Person and Employee. The Employee class extends the DataFlair_Person class. We then define a function called isEmployee that takes an argument of type unknown. The function checks if the input parameter is an instance of the Employee class using the instanceof operator. If the input parameter is an instance of the Employee class, the function returns true. Otherwise, it returns false.
We then create two DataFlair_Person and Employee classes instances and pass them to the isEmployee function. The function correctly identifies the Employee instance as an instance of the Employee class.
3. Custom Type Guard
We can also define our own custom type guards. Custom type guards are useful when we need to check the type of a variable based on a specific set of conditions.
interface DataFlair_Animal {
name: string;
type: string;
}
interface DataFlair_Dog extends DataFlair_Animal {
breed: string;
}
interface DataFlair_Cat extends DataFlair_Animal {
color: string;
}
function isDog(animal: DataFlair_Animal): animal is DataFlair_Dog {
return animal.type === 'dog';
}
function isCat(animal: DataFlair_Animal): animal is DataFlair_Cat {
return animal.type === 'cat';
}
const dog: DataFlair_Dog = {
name: 'Fido',
type: 'dog',
breed: 'Poodle',
};
const cat: DataFlair_Cat = {
name: 'Whiskers',
type: 'cat',
color: 'Black',
};
function printAnimal(animal: DataFlair_Animal) {
if (isDog(animal)) {
console.log(`${animal.name} is a dog of breed ${animal.breed}`);
} else if (isCat(animal)) {
console.log(`${animal.name} is a cat of color ${animal.color}`);
} else {
console.log(`${animal.name} is an unknown animal`);
}
}
printAnimal(dog); // Output: Fido is a dog of breed Poodle
printAnimal(cat); // Output: Whiskers is a cat of color Black
The above example defines three interfaces, DataFlair_Animal, DataFlair_Dog, and DataFlair_Cat. We then define two functions called isDog and isCat. These functions take an argument of type DataFlair_Animal and use a set of conditions to determine if the input parameter is a DataFlair_Dog or DataFlair_Cat object.
We then define two objects, a DataFlair_Dog and a DataFlair_Cat, and pass them to a function called printAnimal. The printAnimal function takes an argument of type DataFlair_Animal and uses the isDog and isCat functions to determine the input parameter type. If the input parameter is a DataFlair_Dog object, the function logs a message to the console indicating the dog’s name and breed.
If the input parameter is a DataFlair_Cat object, the function logs a message to the console that indicates the name and color of the cat. If the input parameter is neither a DataFlair_Dog nor a DataFlair_Cat object, the function logs a message to the console that indicates the animal is unknown.
4. Union Type Guard
The union type guard checks if a variable is of a specific type that is part of a union type. Union types are types that allow a variable to have multiple types.
interface DataFlair_Square {
kind: 'square';
size: number;
}
interface DataFlair_Rectangle {
kind: 'rectangle';
width: number;
height: number;
}
interface DataFlair_Circle {
kind: 'circle';
radius: number;
}
type Shape = DataFlair_Square | DataFlair_Rectangle | DataFlair_Circle;
function area(shape: Shape) {
if (shape.kind === 'square') {
return shape.size * shape.size;
} else if (shape.kind === 'rectangle') {
return shape.width * shape.height;
} else {
return Math.PI * shape.radius * shape.radius;
}
}
const square: DataFlair_Square = {
kind: 'square',
size: 5,
};
const rectangle: DataFlair_Rectangle = {
kind: 'rectangle',
width: 10,
height: 20,
};
const circle: DataFlair_Circle = {
kind: 'circle',
radius: 5,
};
console.log(area(square)); // Output: 25
console.log(area(rectangle)); // Output: 200
console.log(area(circle)); // Output: 78.53981633974483
In the above example, we define three interfaces, DataFlair_Square, DataFlair_Rectangle, and DataFlair_Circle. We then define Shape, a union type of DataFlair_Square, DataFlair_Rectangle, and DataFlair_Circle. We also define a function called area that takes an argument of type Shape.
The area function uses a switch statement to determine the type of the input parameter. If the input parameter is a DataFlair_Square object, the function calculates the area of the square by multiplying the size property by itself. If the input parameter is a DataFlair_Rectangle object, the function calculates the area of the rectangle by multiplying the width property by the height property. If the input parameter is a DataFlair_Circle object, the function calculates the area of the circle using the Math.PI constant and the radius property.
We then create three objects, a DataFlair_Square, a DataFlair_Rectangle, and a DataFlair_Circle, and pass them to the area function. The function correctly calculates the area of each object.
Type Guards and Differentiating Types
TypeScript provides a feature called type guards that allow developers to differentiate between types at runtime. A type guard is a function or an expression that returns a boolean value and performs a type check on its argument. If the type check is successful, the type of the argument is narrowed down to the expected type.
Type guards can be useful when working with union types, where a variable could have multiple types. By using a type guard, we can narrow down the type of the variable to a specific type and access its properties and methods safely.
For example, suppose we have a function that takes a string or a number as an argument, and we want to differentiate between the two types:
function DataFlair_myFunction(input: string | number): void {
if (typeof input === 'string') {
console.log(input.length);
} else {
console.log(input.toFixed(2));
}
}
In the above example, we use the typeof operator as a type guard to differentiate between the string and number types. If the input is a string, we access its length property. If the input is a number, we use the toFixed method to round it to two decimal places.
Type guards can also be implemented using instanceof or by defining custom type predicates. Type predicates are functions that return a boolean value and are used to perform type checks.
Overall, type guards and differentiating types in TypeScript can be useful techniques to ensure the code is correct and type-safe. They allow developers to handle union types and ensure that the types of variables are narrowed down to their expected types. However, using them carefully and only when necessary to prevent potential runtime errors is important.
User defined type guard in TypeScript
User-defined type guards are functions that perform custom type checks and return a boolean value. They allow developers to create type guards to handle complex types or custom logic.
To define a user-defined type guard in TypeScript, we must create a function that takes a parameter and returns a boolean value. The parameter’s type should be a union type of the types we want to differentiate between.
For example, suppose we have a type DataFlair_MyType that could be either a string or an object with a name property. We can define a user-defined type guard to differentiate between the two types:
type DataFlair_MyType = string | { name: string };
function isString(input: DataFlair_MyType): input is string {
return typeof input === 'string';
}
function myFunction(input: DataFlair_MyType): void {
if (isString(input)) {
console.log(input.length);
} else {
console.log(input.name);
}
}
In the above example, we define a user-defined type guard called isString that checks if the input is a string by using the typeof operator. We then use the input string syntax to tell TypeScript that the input type is now narrowed down to a string.
We can then use the isString type guard in the myFunction function to differentiate between the string and { name: string } types. If the input is a string, we access its length property. If the input is an object with a name property, we access its name property.
Overall, user-defined type guards can be useful when working with complex types or custom logic. They allow developers to create their type guards and ensure that the types of variables are narrowed down to their expected types. However, using them carefully and only when necessary to prevent potential runtime errors is important.
Nullable type
In TypeScript, the null and undefined values are commonly used to indicate the absence of a value. However, these values can lead to errors if not handled properly, especially with strict type checking.
To handle nullable values in TypeScript, we can use the nullable type (type | null | undefined). The nullable type allows us to declare that a variable could have a value of the specified type, or it could be null or undefined.
For example, suppose we have a function that takes a string as an argument and returns the length of the string. However, the function should also be able to handle nullable values:
function DataFlair_getStringLength(input: string | null | undefined): number {
if (input === null || input === undefined) {
return 0;
} else {
return input.length;
}
}
In the above example, we use the nullable type (string | null | undefined) to indicate that the input could be a string, null, or undefined. We then check if the input is null or undefined and return 0. Otherwise, we return the length of the input string.
By using the nullable type in TypeScript, we can handle nullable values safely and avoid potential errors caused by null or undefined values. However, using the nullable type carefully and only when necessary is important to prevent unnecessary complexity and potential errors.
TypeScript instanceof type guard
In TypeScript, the instanceof operator can be used as a type guard to differentiate between different types of objects. The instanceof operator checks if an object is an instance of a particular class and returns a boolean value.
For example, suppose we have a Car class and a Truck class inherited from a Vehicle class. We can use the instanceof operator to differentiate between Car and Truck objects:
class DataFlair_Vehicle {
// some properties and methods
}
class Car extends DataFlair_Vehicle {
// some properties and methods
}
class Truck extends DataFlair_Vehicle {
// some properties and methods
}
function getVehicleType(vehicle: DataFlair_Vehicle): string {
if (vehicle instanceof Car) {
return 'Car';
} else if (vehicle instanceof Truck) {
return 'Truck';
} else {
return 'Unknown';
}
}
In the above example, we define a function called getVehicleType that takes a DataFlair_Vehicle object as an argument and returns a string indicating the type of the vehicle. We use the instanceof operator to check if the vehicle object is an instance of a Car or Truck class and return the appropriate string.
The instanceof type guard can be useful when working with classes and objects, allowing us to differentiate between different types of objects and ensuring that their types are properly narrowed down. However, it is important to use the instanceof operator carefully and only when necessary. It can be less flexible than other guards and may lead to runtime errors if not used properly.
Literal typeguard in TypeScript
In TypeScript, a literal type guard is a type guard that uses a specific value to narrow down the type of a variable or parameter. Literal types are types that represent a single value, such as string literals, number literals, or boolean literals.
For example, suppose we have a function that takes a string parameter and we want to ensure that the parameter can only be a specific value. We can use a literal type guard to narrow down the type of the parameter to the specific string value:
function DataFlair_greet(name: string): string {
if (name === 'Alice') {
return 'Hello, Alice!';
} else if (name === 'Bob') {
return 'Hello, Bob!';
} else {
return 'Hello, stranger.';
}
}
In the above example, we use a string literal type guard to narrow down the type of the name parameter to the specific string values of “Alice” or “Bob.” We can use a literal type guard to ensure that the function only accepts the specified string values as valid input.
Literal type guards can also be used with other literal types, such as number or boolean literals, to narrow down the type of a variable or parameter to a specific value. By using literal type guards, we can ensure that our code is more type-safe and less prone to errors caused by unexpected values.
“Equality narrowing” Type Guard
In TypeScript, “equality narrowing” is a type guard technique that allows narrowing down the type of a variable or parameter based on its value using strict equality operators (=== and !==).
For example, suppose we have a function that takes a parameter that can be either a number or a string, and we want to ensure that the parameter is a number. We can use equality narrowing to narrow down the type of the parameter to a number:
function DataFlair_calculateLength(value: number | string): number {
if (typeof value === 'number') {
return value;
} else {
return value.length;
}
}
In the above example, we use the typeof operator and strict equality (===) to check if the value parameter is a number. If it is, we simply return the value as a number. If it is not, we use the .length property to return the string value’s length.
Equality narrowing can also be used with other types, such as boolean, null, or undefined, to narrow down the type of a variable or parameter based on its value. By using equality narrowing, we can ensure that our code is more type-safe and less prone to errors caused by unexpected values.
Conclusion
TypeScript type guards are an important feature that allows us to write safer and more reliable code. By using type guards, we can ensure that our variables are of the correct type at runtime, which helps prevent errors and makes our code easier to maintain.
In this article, we covered four types of type guards: typeof type guard, instanceof type guard, custom type guard, and union type guard. Each type guard has its use case and can be used to check the type of a variable based on different conditions.
TypeScript type guards are easy to implement and can be used to improve the quality and reliability of our code. We can write more efficient and effective TypeScript applications by understanding how type guards work.
Did we exceed your expectations?
If Yes, share your valuable feedback on Google

