TypeScript - Beware the user-defined type guards

This post is part of an ongoing series on TypeScript. In it, I try to explain in a detailed way different situations I’ve faced while building relatively big React applications using TypeScript, and moving them from JavaScript. Here’s a list of other posts I’ve written about TypeScript:

The is keyword in TypeScript is used to specify that a function is a type predicate. Here’s the Wikipedia entry for predicates:

In mathematical logic, a predicate is commonly understood to be a Boolean-valued function P: X → {true, false}, called the predicate on X. […] Informally, a predicate is a statement that may be true or false depending on the values of its variables.

A type predicate is a function returning a boolean value that can be used to identify the type of an expression. In TypeScript, type predicates can be expressed using the following syntax:

function isCollie(dog: Dog): dog is Collie {
  return dog.breed === 'collie' // Must be a Collie

Type guards can be implemented using the instanceof and typeof JavaScript operators, but can also be implemented with the is keyword like in the example above. Type guards of this nature are user-defined type guards.

While useful, user-defined type guards are a vector for runtime errors in TypeScript applications. It’s very easy to implement them in a way that makes a piece of code unsafe. Unsuspecting developers that use them from there on may expand the surface of unsafety. Here’s some example code:

interface Animal {}

interface Bird extends Animal {
  fly: Function

interface Dog extends Animal {
  bark: Function

function isBird(animal: Animal): animal is Bird {
  return true // If you say so...

const norbert: Dog = { bark: () => console.log('Wharg!') }

if (isBird(norbert)) {
  norbert.fly() // Compiles, error during runtime

Granted, that’s a sloppy snippet, but it should serve to illustrate the example. Out in the wild, there are some things you can try to keep in mind in order to avoid this type of problem:

  • Implement type guards in a way that leaves out opportunities for runtime failure.
    • Make use of TypeScript’s implementations around discriminated unions and the type inference they offer.
    • Use them for simpler cases like filtering out null and undefined.
  • Read the implementation of type guards you use (but haven’t implemented) critically.
  • Remember that after all, your application is still a JavaScript application.

Related documentation and links: