Built-in Type Guards: typeof, instanceof

Table of Contents

  • Introduction
  • What Are Type Guards in TypeScript?
  • typeof Type Guard
    • Definition and Syntax
    • Use Cases and Examples
  • instanceof Type Guard
    • Definition and Syntax
    • Use Cases and Examples
  • Practical Examples
    • Example 1: Using typeof to Narrow Types
    • Example 2: Using instanceof to Narrow Types
  • When and Why to Use Type Guards
  • Conclusion

Introduction

TypeScript is a statically-typed superset of JavaScript, and it provides powerful type-checking features that improve the reliability of your code. One of the most helpful features of TypeScript is type guards, which allow you to narrow down the type of a variable within a certain block of code. This is particularly useful when you’re working with unions or any type, and you want to check the type of a value before performing specific operations.

In this article, we’ll focus on two built-in type guards in TypeScript: typeof and instanceof. These are simple yet powerful tools that enable you to check and narrow the types of variables effectively.


What Are Type Guards in TypeScript?

Type guards are expressions that allow TypeScript to narrow down the type of a variable within a certain scope. TypeScript uses these guards to refine the type of a variable when the code runs, allowing for more precise type checking and safer operations.

Type guards can be built-in (like typeof and instanceof), user-defined (such as custom functions that return a boolean), or even inferred by TypeScript itself based on context.

In this article, we will specifically focus on the two most commonly used built-in type guards: typeof and instanceof.


typeof Type Guard

Definition and Syntax

The typeof operator is used to determine the type of a primitive value (e.g., number, string, boolean, etc.). In TypeScript, typeof can also be used as a type guard to narrow down the type of a variable based on its value.

The syntax for typeof is:

if (typeof variable === "type") {
// TypeScript knows the type of 'variable' inside this block
}

Here, the typeof operator checks the runtime type of the variable, and if it matches the specified type (e.g., "string", "number", "boolean", etc.), TypeScript narrows the type of that variable.

Use Cases and Examples

  • Primitives: typeof is particularly useful when you are working with primitive types, such as string, number, boolean, symbol, undefined, and object. It can help refine the type when you deal with union types or variables of type any.

Example:

function printValue(value: number | string) {
if (typeof value === "string") {
console.log(value.toUpperCase()); // Here, TypeScript knows 'value' is a string
} else if (typeof value === "number") {
console.log(value.toFixed(2)); // Here, 'value' is narrowed to a number
}
}

printValue("hello"); // Output: HELLO
printValue(123.456); // Output: 123.46

In this example, typeof is used to check if value is a string or number, and based on that, TypeScript knows the exact type of value inside each conditional block, allowing you to safely call methods specific to each type (e.g., toUpperCase for strings and toFixed for numbers).


instanceof Type Guard

Definition and Syntax

The instanceof operator is used to check if an object is an instance of a specific class or constructor function. It’s particularly useful when you want to narrow the type of a class-based object.

The syntax for instanceof is:

if (variable instanceof Class) {
// TypeScript narrows the type of 'variable' to 'Class' inside this block
}

Here, instanceof checks whether variable is an instance of the Class. If it is, TypeScript narrows the type of variable to that class type, allowing access to class-specific properties and methods.

Use Cases and Examples

  • Class Instances: instanceof is used to check whether an object is an instance of a particular class, allowing you to work with class-specific properties and methods without TypeScript complaining about incorrect types.
  • Inheritance: instanceof can also be used to check if an object is an instance of a subclass, making it easy to handle objects that inherit from a common base class.

Example:

class Car {
constructor(public model: string, public year: number) {}
}

class Bike {
constructor(public brand: string, public type: string) {}
}

function printVehicleInfo(vehicle: Car | Bike) {
if (vehicle instanceof Car) {
console.log(`Car Model: ${vehicle.model}, Year: ${vehicle.year}`);
} else if (vehicle instanceof Bike) {
console.log(`Bike Brand: ${vehicle.brand}, Type: ${vehicle.type}`);
}
}

const myCar = new Car('Toyota', 2021);
const myBike = new Bike('Yamaha', 'Sport');

printVehicleInfo(myCar); // Output: Car Model: Toyota, Year: 2021
printVehicleInfo(myBike); // Output: Bike Brand: Yamaha, Type: Sport

In this example, instanceof is used to check whether the vehicle is an instance of Car or Bike, and TypeScript narrows the type accordingly inside each if block. This ensures type-safe access to properties specific to each class.


Practical Examples

Example 1: Using typeof to Narrow Types

function processValue(value: string | number) {
if (typeof value === "string") {
console.log(value.length); // 'value' is narrowed to a string here
} else {
console.log(value.toFixed(2)); // 'value' is narrowed to a number here
}
}

processValue("hello"); // Output: 5
processValue(123.456); // Output: 123.46

In this example, typeof allows TypeScript to refine the type of value based on whether it’s a string or a number, enabling safe method calls like length for strings and toFixed for numbers.

Example 2: Using instanceof to Narrow Types

class Shape {
constructor(public name: string) {}
}

class Circle extends Shape {
constructor(name: string, public radius: number) {
super(name);
}
}

class Rectangle extends Shape {
constructor(name: string, public width: number, public height: number) {
super(name);
}
}

function calculateArea(shape: Shape) {
if (shape instanceof Circle) {
return Math.PI * shape.radius ** 2; // Narrowed to Circle type
} else if (shape instanceof Rectangle) {
return shape.width * shape.height; // Narrowed to Rectangle type
} else {
throw new Error("Unknown shape");
}
}

const circle = new Circle("Circle", 10);
const rectangle = new Rectangle("Rectangle", 20, 30);

console.log(calculateArea(circle)); // Output: 314.1592653589793
console.log(calculateArea(rectangle)); // Output: 600

In this example, instanceof is used to narrow the type of shape to Circle or Rectangle, allowing the correct formula to be applied based on the shape type.


When and Why to Use Type Guards

  • typeof: Use typeof when dealing with primitive types such as strings, numbers, and booleans, or when working with union types. It is a simple and effective way to check the type at runtime.
  • instanceof: Use instanceof when working with class-based types or when dealing with inheritance. It allows TypeScript to infer and narrow the type of objects that are instances of specific classes, ensuring type safety for class methods and properties.

Both typeof and instanceof are integral tools for narrowing down types in TypeScript, especially when working with complex or dynamic data structures.


Conclusion

In this article, we’ve explored two powerful built-in type guards in TypeScript: typeof and instanceof. These operators allow you to narrow the types of variables within specific blocks of code, enabling type-safe operations and reducing runtime errors.

By using typeof for primitive types and instanceof for class instances, you can handle dynamic data structures more effectively and ensure that your code adheres to TypeScript’s type safety features.