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 from Heidelberg, Germany. I am a passionate IT freelancer with 15+ years experience in programming, especially in developing web based applications for companies that range from small startups to the big players out there. I create Bosycom and initiated several software projects.