Aha! Understanding Typescript’s Type Predicates

This article will cover Type Predicates at a high level. To understand Type Predicates, we must first understand how they are used in relation to union types.

Union Types

In Typescript, a variable isn’t always restricted to a single type. Union Types are a way to declare multiple types onto a single value.

// value can now be set to a `string`, `boolean`, or `null` value.
let value: string | boolean | null = ...

interface Cat {
  numberOfLives: number;
}
interface Dog {
  isAGoodBoy: boolean;
}

let animal: Cat | Dog = ...

When we use union types, we have to do work to narrow the possible types down to the current value’s actual type. Type Guards are what allow us to do this narrowing.

Type Guards

According to the official docs,

A type guard is some expression that performs a runtime check that guarantees the type in some scope.

Put another way, type guards guarantee that a string is a string when it could also be a number.

Type guards are not entirely different than doing feature detection. The big idea is to try to detect properties, methods or prototypes to figure out how to handle a value. There are four main ways to use type guards:

  • in keyword
  • typeof keyword
  • instanceof keyword
  • type predicates with custom type guard

Type Predicate

While you are probably familiar with “in”, “typeof”, and “instanceof”, you might be wondering what “type predicates” are. Type predicates are a special return type that signals to the Typescript compiler what type a particular value is. Type predicates are always attached to a function that takes a single argument and returns a boolean. Type predicates are expressed as argumentName is Type.

interface Cat {
  numberOfLives: number;
}
interface Dog {
  isAGoodBoy: boolean;
}

function isCat(animal: Cat | Dog): animal is Cat {
  return typeof animal.numberOfLives === 'number';
}

For sample function, isCat, is executed at run time just like all other type guards. Since this function returns a boolean and includes the type predicate animal is Cat, the Typescript compiler will correctly cast the animal as Cat if isCat evaluates as true. It will also cast animal as Dog if isCat evaluates as false.


let animal: Cat | Dog = ...

if (isCat(animal)) {
  // animal successfully cast as a Cat
} else {
  // animal successfully cast as a Dog
}

Pretty neat! Perhaps the best thing about custom type guards and type predicates is not only we can use in, instanceof, and typeof in our type guards but we can also custom type checks. As long as our function returns a boolean, Typescript will do the right thing.