Understanding and Implementing Type Guards in TypeScript
Tags: typescript, types, typeguards, webdev
Published: 2024-03-11 13:45:46

[TypeScript](https://www.typescriptlang.org/), an open-source language developed and maintained by Microsoft, is a superset of JavaScript that adds static types to the language. It has grown to include such advanced features as [generic types](https://claritydev.net/blog/typescript-generics-guide), [conditional types](https://claritydev.net/blog/advanced-conditional-types-typescript), [template literal types](https://claritydev.net/blog/typescript-type-literals-practical-use-cases-code-quality), [tools for inferring types](https://claritydev.net/blog/typescript-infer-keyword-unlocking-type-information) and [mapped types](https://claritydev.net/blog/typescript-mapped-types-guide), among others. While TypeScript's static typing system is a tremendous asset, it can sometimes be tricky to determine the type of an object at runtime, particularly when dealing with complex data structures or object-oriented programming constructs. This is where type guards come in.

Type guards are a powerful feature in TypeScript that allows you to narrow down the type of an object within a certain scope. With type guards, you can perform specific checks to determine the type of an object and then use that object in a way that is type-safe according to the TypeScript compiler.

For instance, if you have a variable that could be a string or a number, you can use a type guard to check if the variable is a string. If the type guard check passes, TypeScript will then allow you to use the variable as a string within the scope of the type guard check.

Understanding and effectively using type guards is an essential skill when programming in TypeScript. Not only can they help to prevent type-related bugs, but they also make code cleaner and easier to understand. In the following sections, we'll look into the concept of type guards, explore the different types of type guards that TypeScript offers, and learn how to use them in practical scenarios.


## What are type guards

TypeScript is designed to help developers manage the types of their variables and function returns in a way that's more robust than regular JavaScript. When multiple types are possible, such as in union types, the compiler cannot safely predict the type without help. This is where type guards come into play. They are used to inform the compiler about the type of a variable inside a block of code, which allows the compiler to assume the correct type and expose the relevant properties and methods. This is crucial in TypeScript because it allows you to achieve more robust [error handling](https://claritydev.net/blog/typescript-error-handling-and-defensive-programming) and write safer code by ensuring that you're using variables according to their actual types.

For instance, let's say we have a variable that could either be a string or an array of strings. If we want to use a method that is specific to the array type, like `push()`, we would first need to ensure that the variable is indeed an array. We can achieve this using a type guard.

```typescript
let variable: string | string[];

if (Array.isArray(variable)) {
  // Within this if block, TypeScript now knows that 'variable' is an array of strings
  variable.push("new item");
} else {
  // Here, TypeScript knows that 'variable' is a string
  console.log(variable.toUpperCase());
}
```

In this code, `Array.isArray(variable)` is the type guard. If this expression evaluates to `true`, TypeScript knows that `variable` is an array of strings within the following block. If it evaluates to `false`, TypeScript knows that `variable` is a string in the `else` block.

Type guards are essential for writing robust, bug-free TypeScript code. They provide a way to ensure that you're using variables correctly according to their types, and they help the TypeScript compiler understand the types of variables in different parts of your code.

## Types of type guards in TypeScript and practical examples

TypeScript provides several mechanisms to implement type guards. Here, we'll discuss the most commonly used ones - [typeof](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/typeof), [instanceof](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/instanceof), and user-defined type guards - and illustrate their usage with practical examples.

### Using typeof for primitives

The `typeof` type guard is one of the simplest and most common ways to narrow down types in TypeScript. It checks whether a variable is of a certain [primitive](https://developer.mozilla.org/en-US/docs/Glossary/Primitive) type, like `string`, `number`, `boolean` or `symbol`.

```typescript
function processInput(input: string | number) {
  if (typeof input === "string") {
    return input.toUpperCase(); // TypeScript knows 'input' is a string here
  } else {
    return input.toFixed(2); // TypeScript knows 'input' is a number here
  }
}
```

In this function, `input` can either be a string or a number. The `typeof` type guard checks the type of `input`, and depending on the result, the function performs different operations.


### Using instanceof for class instances

The `instanceof` type guard is used for narrowing down types when dealing with classes and their instances. It checks whether an object is an instance of a particular class. This is particularly useful when working with complex object-oriented patterns where an object could be an instance of one of several possible classes in a hierarchy.

```typescript
class Dog {
  bark() {
    return "Woof!";
  }
}

class Cat {
  purr() {
    return "Meow!";
  }
}

function makeSound(animal: Dog | Cat) {
  if (animal instanceof Dog) {
    return animal.bark(); // TypeScript knows 'animal' is a Dog
  } else {
    return animal.purr(); // TypeScript knows 'animal' is a Cat
  }
}
```

In this example, `animal` could be an instance of either the `Dog` or `Cat` class. The `instanceof` type guard checks the class of animal, and the function calls the appropriate method based on the result.

### User-defined type guards

TypeScript also allows you to define your own type guards. These are useful when you want to perform more complex type checks.

A user-defined type guard is a function that returns a type predicate (i.e., a boolean expression that is dependent on a type). Here's how you can define one, on the example of working with [form event types](https://claritydev.net/blog/typescript-typing-form-events-in-react):

```typescript
interface ChangeEvent {
  type: "change";
  target: HTMLInputElement;
  value: string;
}

interface SubmitEvent {
  type: "submit";
  target: HTMLFormElement;
  fields: Record<string, string>;
}

// User-defined type guard to determine if the event is a ChangeEvent
function isChangeEvent(event: ChangeEvent | SubmitEvent): event is ChangeEvent {
  return event.type === "change";
}

// Function to handle form events, utilizing the user-defined type guard
function handleFormEvent(event: ChangeEvent | SubmitEvent) {
  if (isChangeEvent(event)) {
    // The TypeScript compiler now knows that 'event' is a ChangeEvent
    console.log(
      `Handling change event on field: ${event.target.name}, with value: ${event.value}`,
    );
  } else {
    // Since the event is not a ChangeEvent, it must be a SubmitEvent
    console.log("Handling form submit event with fields:", event.fields);
  }
}
```

In this example, `isChangeEvent` is a user-defined type guard that checks if an event is a `ChangeEvent`. The `event is ChangeEvent` syntax is a type predicate that tells TypeScript that the result of the function is a boolean expression that depends on the type of `event`. This allows TypeScript to narrow down the type of `event` within the `handleFormEvent` function based on the result of the type guard check.


## Advanced usage

Type guards are a powerful feature in TypeScript that can be used in a variety of scenarios. In this section, we'll explore some advanced use cases for type guards, including working with union types and discriminated unions.

### Narrowing and discriminated unions with type guards

Discriminated unions are a pattern in TypeScript that allows you to combine union types and literal types to create a type that can be a set of specific values. This is particularly useful in scenarios where you have a variable that could be one of several different shapes.

```typescript
interface Circle {
  kind: "circle";
  radius: number;
}

interface Square {
  kind: "square";
  sideLength: number;
}

type Shape = Circle | Square;
```

In this case, the `Shape` type is a discriminated union of `Circle` and `Square`. The `kind` property acts as a discriminant, allowing you to differentiate between the possible types.

To narrow a discriminated union, you can use a type guard that checks the discriminant property:

```typescript
function getArea(shape: Shape) {
  switch (shape.kind) {
    case "circle":
      return Math.PI * shape.radius ** 2;
    case "square":
      return shape.sideLength ** 2;
  }
}
```

In the `getArea` function, TypeScript can narrow down the type of shape within each case branch based on the `kind` property. This allows you to access the appropriate properties for each type without any type errors.

### Using the "in" operator

The [in](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/in) operator is another way to create type guards in TypeScript. It can be particularly useful when working with objects and there's a need to check if they contain a certain property.

Let's extend our `Shape` example with a new `Rectangle` type:

```typescript
interface Circle {
  kind: "circle";
  radius: number;
}

interface Square {
  kind: "square";
  sideLength: number;
}

interface Rectangle {
  kind: "rectangle";
  width: number;
  height: number;
}

type Shape = Circle | Square | Rectangle;
```

In our `getArea` function, we can now use the `in` operator as a type guard:

```typescript
function getArea(shape: Shape) {
  if ("radius" in shape) {
    // shape is treated as Circle here
    return Math.PI * shape.radius ** 2;
  } else if ("sideLength" in shape) {
    // shape is treated as Square here
    return shape.sideLength ** 2;
  } else if ("width" in shape && "height" in shape) {
    // shape is treated as Rectangle here
    return shape.width * shape.height;
  }
}
```

In this example, the `in` operator checks whether specific properties (`radius`, `sideLength`, `width`, `height`) exist in the `shape` object. Depending on which check passes, TypeScript narrows the type of shape within that branch of the code, allowing us to access the appropriate properties of each shape without any type errors.

### Type guards with mapped types

[Mapped types](https://claritydev.net/blog/typescript-mapped-types-guide) in TypeScript create new types by transforming properties from an existing type, usually involving some operation on each property. When an object's shape is transformed using a mapped type, type guards help in verifying that the object still conforms to a specific structure.

```typescript
// Original type
type OriginalPerson = {
  name: string;
  age: number;
  hasPet: boolean;
};

// Mapped type that makes all properties optional
type PartialPerson = {
  [P in keyof OriginalPerson]?: OriginalPerson[P];
};

// Type guard for PartialPerson
function isPartialPerson(person: any): person is PartialPerson {
  return (
    ("name" in person && typeof person.name === "string") ||
    ("age" in person && typeof person.age === "number") ||
    ("hasPet" in person && typeof person.hasPet === "boolean")
  );
}

// Function that takes an object and a mapped type as arguments
function processPerson<T extends PartialPerson>(person: T): void {
  if (isPartialPerson(person)) {
    // TypeScript safely infers 'person' to be 'PartialPerson' within this block
    console.log("Processed PartialPerson:", person);
  }
}

const maybePerson = {
  name: "Bob",
  age: 30,
};

processPerson(maybePerson); // OK, 'maybePerson' satisfies 'PartialPerson'
```

In the example above, `isPartialPerson` is a type guard that checks if the input `person` object matches the `PartialPerson` mapped type structure. If the check passes, TypeScript narrows the type of `person` within the `processPerson` function, allowing us to safely use it as a `PartialPerson`.

## Common mistakes and best practices

Type guards in TypeScript are a powerful tool for ensuring type safety in your code. However, like any tool, they must be used correctly to be effective. In this chapter, we'll look at some common mistakes developers make when using type guards and discuss best practices for using them effectively.

- **Over-reliance on `any` type**: One of the most common mistakes in TypeScript development is overusing the `any` type, which essentially bypasses TypeScript's type checking. Instead of relying on this catch-all type, when using type guards, always aim to use precise types.

- **Incorrect use of `typeof` and `instanceof`**: These type guards are beneficial but should be used correctly. For instance, `typeof` is best suited for primitive types (like `string`, `number`, `boolean`), while `instanceof` is appropriate for custom classes. It's important to understand the correct usage of these type guards and apply them properly in your code.

- **Not considering all possible types in a union**: When using type guards with union types, a common mistake is not handling all possible types within the union. This oversight could lead to runtime errors. To counter this, always handle all possible types in a union. One approach is to use exhaustive type checking, using a `never` type in a default case in a switch statement. This technique will cause TypeScript to throw an error if there are unhandled cases, helping to prevent bugs.

- **Missing out on user-defined type guards**: TypeScript offers the ability to create custom type guards, but developers often overlook this feature. User-defined type guards are especially beneficial when working with complex types as they can encapsulate complicated type-checking logic, improving code readability and maintainability.

- **Not using discriminated unions for complex types**: For complex types with shared fields, developers sometimes miss the opportunity to use discriminated unions. This misstep can lead to more complicated and error-prone type-checking code. Discriminated unions allow you to differentiate between types based on a common "tag" or "kind" field, making it easier to handle different types in a type-safe manner. Always consider using discriminated unions when dealing with complex types.

By avoiding common pitfalls and adhering to best practices, you can make effective use of type guards to improve the safety and reliability of your TypeScript code. It's well worth investing the time to understand and correctly use this powerful feature of the TypeScript language.


## Conclusion

In this article, we've explored the importance and usage of type guards in TypeScript. From understanding what type guards are to diving into advanced topics like union types and discriminated unions, hopefully, you've gained a deeper understanding of this crucial aspect of TypeScript's type system.

Type guards are a powerful tool that can help you write safer, more reliable code. While they can seem complex at first, with practice and understanding, they become an invaluable part of your TypeScript toolkit.

By leveraging the power of type guards, you can take full advantage of TypeScript's static typing, leading to better, more predictable, and less error-prone code.

## References and resources

- [Error Handling and Defensive Programming with TypeScript](https://claritydev.net/blog/typescript-error-handling-and-defensive-programming)
- [MDN: primitives](https://developer.mozilla.org/en-US/docs/Glossary/Primitive)
- [MDN: the "in" operator](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/in)
- [MDN: the "instanceof" operator](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/instanceof)
- [MDN: the "typeof" operator](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/typeof)
- [TypeScript Advanced Types: Working with Conditional Types](https://claritydev.net/blog/advanced-conditional-types-typescript)
- [TypeScript Template Literal Types: Practical Use-Cases for Improved Code Quality](https://claritydev.net/blog/typescript-type-literals-practical-use-cases-code-quality)
- [TypeScript website](https://www.typescriptlang.org/)
- [TypeScript's Infer Keyword: Unlocking Type Information](https://claritydev.net/blog/typescript-infer-keyword-unlocking-type-information)
- [TypeScript: Typing Form Events In React](https://claritydev.net/blog/typescript-typing-form-events-in-react)
- [TypeScript: Using type predicates](https://www.typescriptlang.org/docs/handbook/2/narrowing.html#using-type-predicates)
- [Unlocking the Power of TypeScript's Mapped Types](https://claritydev.net/blog/typescript-mapped-types-guide)
- [Using Generics In TypeScript: A Practical Guide](https://claritydev.net/blog/typescript-generics-guide)