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.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
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);
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);
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:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
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);
}
}
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); } }
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

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
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"
});
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" });
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

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
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"
});
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" });
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:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
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
}
}
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 } }
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:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
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
}
}
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 } }
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.