What are type Guards in Typescript

avatar

Borislav Hadzhiev

Sat Feb 20 20213 min read

banner

Photo by Ryan Stone

Type Guards are necessary conditional blocks that narrow down the types of the value we're working with

Type Guards theory #

We use type guards in typescript when we have logic that is specific to a particular type.

In other words:

if (typeof someValue === 'string') {
  // someValue is guaranteed to be of type string
  // => therefore we can perform all string operations with it
  // like call someValue.toLowerCase(), etc.
}

We want to force typescript to allow us to work with a value as if it's guaranteed to be of type "x". Once we run a type guard, we clarify to typescript the type of value we are working with.

Type Guards example #

function sum(a: string | number, b: string | number): string | number {
  // a and b are either of type string or number
  // therefore we can only use methods on them,
  // that are common between strings and numbers
  // i.e. .toString() and .valueOf()
  if (typeof a === 'string' && typeof b === 'string') {
    // both a and b are guaranteed to be of type string
    // can call all string methods on them
    return a.toUpperCase() + b.toUpperCase();
  }

  if (typeof a === 'number' && typeof b === 'number') {
    // both a and b are guaranteed to be of type number
    // can call all number methods on them
    return a.toFixed(2) + b.toFixed(2);
  }

  if (typeof a === 'string' || typeof b === 'string') {
    // we don't know if a or b is the string,
    // so we don't get to call string methods on either one of them
    return String(a) + String(b);
  }

  // we have exhausted all the possibilities
  // Now a and b are guaranteed to be of type 'number'
  return a.toFixed(2) + b.toFixed(2);
}

We often use type guards when working with union types, if we have an either or scenario and we have to narrow down the type of value we're working with to make sure the operations we're trying to perform will be type safe.

Checking primitives vs checking objects #

In the above example we used typeof to narrow down the type and use a type guard, we only use typeof with primitive values:

  • if (typeof someNumber === 'number') {guaranteed to be a number}
  • if (typeof someString === 'string') {guaranteed to be a string}
  • if (typeof someBoolean === 'boolean') {guaranteed to be of type boolean}

For non primitive types we use instanceof:

  • if (someObject instanceof someClass) {guaranteed to be of type someClass}
function loop(collection: string[] | string): void {
  // can only access common methods to both
  // string[] and string
  if (Array.isArray(collection)) {
    // collection is guaranteed to be a string[]
    collection.push('hello world');
  }

  // The above is the same as
  if (collection instanceof Array) {
    // collection is guaranteed to be a string[]
    collection.push('hello world');
  }

  if (typeof collection === 'string') {
    // collection is guaranteed to be a string
    console.log(collection.toUpperCase());
  }
}

Even though typeof would work with an object, it does not work as a type guard in typescript. For example typeof an array in javascript would return "object", but it does not function as a type guard in typescript.

We use type guards anytime we want to narrow down the type of a union type and restore a set of type specific properties on the value.

Join my newsletter

I'll send you 1 email a week with links to all of the articles I've written that week

Buy Me A Coffee