This article's content
Type Guards in TypeScript

Type Guards are used to check whether you are dealing with a certain type or not. Getting from a more general type to a more specific type is called Narrowing. There are different ways to 'type guard', depending on what to test against. You can test against native values (string, number, boolean etc.), instances of classes or custom types.

Type Guard for native values

Here we check if year is a string or a number using typeof.

function showNextYear(year: string | number) {
    if(typeof year === "string") {
        const nextYear = parseInt(year, 10) + 1;
        console.log(`Next year is ${nextYear}`);
    } else {
        const nextYear = year + 1;
        console.log(`Next year is ${nextYear}`);
    }
}

showNextYear("2021");
showNextYear(2021);

Truthiness narrowing

Here we narrow down the type by checking the truthiness of strs:

function printAll(strs: string | string[] | null) {
  if (strs && typeof strs === "object") {
    for (const s of strs) {
      console.log(s);
    }
  } else if (typeof strs === "string") {
    console.log(strs);
  }
}

Instanceof Type Guard

class Deed {
    constructor(public action: string) { }
}

class Good extends Deed {
    constructor(action: string, public goodThing: string) {
        super(action);
    }
}

class Bad extends Deed {
    constructor(action: string, public badThing: string) {
        super(action);
    }
}

function yourAction(deed: Good | Bad) {
    if(deed instanceof Good) {
        console.log("Well done");
    } else {
        console.log("Please stop");
    }
}

yourAction({
    action: "donate",
    goodThing: "money"
});

yourAction({
    action: "waste",
    badThing: "food"
});

Custom Type Guard

interface Deed {
    action: string;
}

interface Good extends Deed {
    goodThing: string
}

interface Bad extends Deed {
    badThing: string
}

function isGood(deed: Deed): deed is Good {
    return deed.hasOwnProperty("goodThing");
}

function yourAction(deed: Good | Bad) {
    if(isGood(deed)) {
        console.log("Well done");
    } else {
        console.log("Please stop");
    }
}

yourAction({
    action: "donate",
    goodThing: "money"
});

yourAction({
    action: "waste",
    badThing: "food"
});

More narrowing

TypeScript is rather smart about automatically narrowing down types. In the following example we check if x is the same as y. If so, they both must be strings:

function example(x: string | number, y: string | boolean) {
  if (x === y) {
    // We can now call any 'string' method on 'x' or 'y'.
    x.toUpperCase();
          
(method) String.toUpperCase(): string
    y.toLowerCase();
          
(method) String.toLowerCase(): string
  } else {
    console.log(x);
               
(parameter) x: string | number
    console.log(y);
               
(parameter) y: string | boolean
  }
}

Same goes for in operator:

type Fish = { swim: () => void };
type Bird = { fly: () => void };
type Human = {  swim?: () => void, fly?: () => void };

function move(animal: Fish | Bird | Human) {
  if ("swim" in animal) { 
    animal
      
(parameter) animal: Fish | Human
  } else {
    animal
      
(parameter) animal: Bird | Human
  }
}

About Author

Mathias Bothe To my job profile

I am Mathias, born 40 years ago in Heidelberg, Germany. Today I am living in Munich and Stockholm. I am a passionate IT freelancer with more than 16 years experience in programming, especially in developing web based applications for companies that range from small startups to the big players out there. I am founder of bosy.com, creator of the security service platform BosyProtect© and initiator of several other software projects.