Go from beginner to pro in TypeScript’s type guards

Code
Photo by Arnold Francisca on Unsplash.

As most of you know, TypeScript is a language that was built by Microsoft in 2012. It is a JavaScript superset and dynamically typed. Since it’s dynamically typed, type guards sometimes play a big role.

The Basics

What are type guards anyway? They are a set of tools that help us narrow down the type of objects. This means that you are going from a more general type to a more specific one. Their most common usage is to narrow down union types.

There are multiple techniques to perform type guards. Choosing the right one will depend on your specific scenario and personal preference. We will be exploring each of these techniques and their caveats.

You can use the TypeScript Playground app to run all the snippets included in this article. It is a nice tool with which you can practice and improve your TypeScript skills.

1. Nullable Types

Let’s start with the easiest one. Let’s say you have a type that can be nullable. Anytime you are doing a null, you are basically doing a type guard. If you are already doing null checks, you are using already type guards intuitively:

It’s very important to understand how type guards work with conditional blocks. In a simple condition block (like above), the type guard will only apply inside of it. However, if you do a return statement inside that conditional type guard, TypeScript is smart enough to remove that type from the union outside that conditional block:

Type guards are not so scary now, are they?

2. === and !== Operators

typeof is a basic tool in your daily TypeScript usage. It’s most frequently used to extract the TypeScript type from a value object. It does port the value object back to TypeScript’s type.

Don’t be confused, as JavaScript has its own typeof operator that returns a string indicating the type of the unevaluated operand.

// Typescript
const literal = 'literal type';
type literal = typeof literal; // literal = 'literal type'
// Javascript
const literal = 'literal type';
typeof literal; // 'string'

Another example of extracting types with typeof:

interface Book {
author: string;
}
const b: Book = { author: 'jose granja'};
type C = typeof b; // we have extracted the type Book from b

You can combine typeof with the operators !== or === to perform type guards.

Let’s create our own fizzBuzz with a string | number type union entry parameter. When the variable is a string, we will print Fizz. When it’s a number, we will print Buzz:

Without the typeof operator, we couldn’t extract the type from x. That’s why it’s so important, basic, and powerful.

3. Instanceof

You are probably familiar with JavaScript instanceof operator. This operator is similar to typeof, but its usage is focused on ECMAScript classes. The right-hand operator needs to be a class. Otherwise, we will get an error:

4. Type Assertions

These are the most manual ones. They are based on the developer’s knowledge of the code. That’s why they should be avoided as much as possible. However, they are a tool at your disposal and you should be aware of them.

function addOne(x: unknown) {
// devs knows x will always be a number
return x as number + 1;
}

You can manually change the variable type by using the as keyword. You can go from a generic type to a more specific one. However, if the two types are unrelated, you will need to convert to unknown/any first:

const x: number = 12;
const a: string = x; // Type 'number' is not assignable to type 'string'
const b: string = x as unknown as string; // Type 'number' was converted to 'string'

In the example above, the typing is wrong, as we are assigning a number to a string. The compiler does trust us even if it looks wrong, as we are very specific about it. That’s why this kind of casting should be avoided.

In the addOne example above, we can remove the type guard if we type properly type the function param:

function addOne(x: number) {
return x + 1; // no type guard needed
}

5. Literals and Enums

You can use literal types to work with type guards:

This didn’t look all that fancy… but let’s have that 'hunter' | 'pray' be the key to type-guard some interfaces.

Let’s first define two similar interfaces that have the same kind property but with different types. That way, we can know which interface is the invoker’s type by asserting the kind property. TypeScript will automatically distinguish between the union types and assign the correct one inside the function’s scope:

We could do this in a cleaner way by using enum in TypeScript. Enum makes our code less prone to type and typo errors:

6. User-Defined Using Predicates

These are a bit more sophisticated than the previous ones. You can define assertions like functions for any given type. We just need to define a function whose return type is a type predicate and it returns true/false:

Note that the function isHunter’s return type is x is Hunter. That’s because it’s an assertion function and that’s why it returns true or false.

Inside the code block isHunter(x), the x variable is of type Hunter. That means we can safely call its hunt method. However, outside this code block, the x type will be still unknown.

7. User-Defined Using in Operator

There’s another way we can go about it, and it’s to use the in operator. JavaScript does have a similar operator and it behaves in a similar way. Its purpose is to check if a given property exists in the interface/type.

Let’s redo the previous example but with the in usage this time:

We can differentiate betweenHunter/Pray types by checking the existence of the method hide or hunt.

When to use this approach vs. the previous one will depend on code style and the use case.

Type Guards and Callback

There’s a special behavior with type guards and callbacks that is worth knowing about. Let’s check it out with some examples:

let x: string | number = "a";
if (typeof x === "string") {
console.log(x.split(' ')[0]); // x is of type string as expected
}

If we use a mutable variable like x inside a callback, TypeScript will not take the type guard into account. It will see x as a string | number inside the setTimeout callback and therefore throw an error:

That is because TypeScript is not certain when the call will be called, and in that period of time, x can change its type. TypeScript can’t possibly tell if the variable is going to change its type or not, so it gives an error as a precaution:

This doesn’t happen if the variable is not mutable. If, instead of let x, we define it as const x, then no error will be thrown since that variable’s value is sealed:

Wrapping Up

Throwing hats at graduation
Photo by Vasily Koloda on Unsplash.

We have seen the different ways in which we can use type guards. As a rule of thumb, you shouldn’t be abusing them.

If you are, you should analyze why you need them so often. There’s probably a bigger problem happening behind the scenes. They are just meant to be used in some particular scenarios.

If you want to keep improving your TypeScript skills, I wrote an article about Mapped Types that is worth a look:

Mastering TypeScript’s Mapped Types

More TypeScript content will be coming in the future. Cheers!


Master TypeScript’s Type Guards was originally published in Better Programming on Medium, where people are continuing the conversation by highlighting and responding to this story.