typescript: forms of type guard functions

2021-10-09

 | 

~2 min read

 | 

395 words

I’ve written about type guards a few times before - here and here - but I thought I’d collect a few different ways I’ve found to use type guards.

It’s worth remembering why we want to use type guards. They’re useful for type narrowing. We’re looking for strategies to help the compiler know what kind of type we’re working with.

The most common approaches I’ve found split along the line of how the types are joined:

  • With a ”type” property
  • Without a type property

With A Type Property

When we’re trying to narrow a union type where the subtypes share a common property, e.g., type (e.g., an enum or string literal type).

Let’s think of a fairly simple employee architecture where we store different information for standard employees from administrators.

enum Role {
  Standard = "Standard",
  Admin = "Admin",
}

type Standard = {
  role: Role.Standard
  name: string
  age: number
}

type Admin = {
  role: Role.Admin
  name: string
  securityLevel: "Normal" | "Elevated" | "High"
}

type Employee = Admin | Standard

Now, if I have a function that takes an Employee, before I can access certain attributes (age and securityLevel), I need to narrow to Standard and Admin respectively.

Because we have enriched our employee subtypes to include a role, we just need to look at that to see what kind of employee we’re dealing with.

A type guard works perfectly for that:

function isAdmin(employee: Employee): employee is Admin {
  return employee.role === Role.Admin
}

Without A Type key

What about the situation where the types aren’t included as a convenience?

type Standard = {
  name: string
  age: number
}

type Admin = {
  name: string
  securityLevel: "Normal" | "Elevated" | "High"
}

type Employee = Admin | Standard

Well, now we need to key off the unique attributes. But, we can’t just inspect them or Typescript will complain.

We still need to narrow, but using the in keyword, we can do this:

function isAdmin(employee: Employee): employee is Admin {
  return "securityLevel" in employee
}

This works because securityLevel is a unique attribute of the Admin employee. If it’s present, isAdmin returns true. If it’s absent, the function returns false.

Wrap Up

These are just a few different ways to narrow our types using type guards (by defining a type predicate).


Hi there and thanks for reading! My name's Stephen. I live in Chicago with my wife, Kate, and dog, Finn. Want more? See about and get in touch!