Skip to main content
eLearner.app
Module 5 · Lesson 1 of 29/14 in the course~15 min
Module lessons (1/2)

Type Guards

Type narrowing is one of the most important concepts in TypeScript. Often, however, standard type guards like typeof or instanceof are not sufficient for complex custom objects. This is where Custom Type Guards come in.

Standard Type Guards

Let's begin by summarizing how TypeScript narrows types using standard JavaScript operators:

TS
function processInput(val: string | number) {
  if (typeof val === 'string') {
    // Here 'val' is of type string
    console.log(val.toUpperCase());
  } else {
    // Here 'val' is of type number
    console.log(val.toFixed(2));
  }
}

Custom Type Guards (is operator)

To define a custom type guard, we create a function that returns a type predicate in the format parameterName is Type instead of a standard boolean return type.

The function must return a boolean (true or false). If it returns true, TypeScript will know that the passed parameter is of the specified type.

TS
interface Cat {
  name: string;
  meow(): void;
}

interface Dog {
  name: string;
  bark(): void;
}

// This is a custom type guard
function isCat(animal: Cat | Dog): animal is Cat {
  return (animal as Cat).meow !== undefined;
}

function makeNoise(pet: Cat | Dog) {
  if (isCat(pet)) {
    // Here TypeScript knows 'pet' is a Cat
    pet.meow();
  } else {
    // Here TypeScript knows 'pet' is a Dog
    pet.bark();
  }
}

Safety and Casting (as vs Type Guards)

Type casting (e.g., val as Cat) forces the compiler to trust the developer without any real runtime verification. If the runtime object does not meet expectations, the code will fail silently or throw exceptions.

Custom type guards, on the other hand, perform a robust and dynamic runtime check, safely and accurately informing the TypeScript compiler of the actual variable type:

TS
function processInput(input: unknown) {
  // unsafe casting: could crash if input does not have the split method
  // const str = input as string;
  // console.log(str.split(' '));

  // safe guard
  if (isString(input)) {
    console.log(input.split(' ')); // 100% Safe!
  }
}

Try it yourself

Exercise 1: String Type Guard

Exercise#ts.m5.l1.e1
Attempts: 0Loading…

Create a simple function named isString that acts as a custom type guard to verify if an unknown value is a string.

Loading editor…
Show hint

The return type of the function must be val is string, and you should use typeof to check if it equals 'string'.

Solution available after 3 attempts

Exercise 2: Premium User Type Guard

Exercise#ts.m5.l1.e2
Attempts: 0Loading…

Given the User and PremiumUser types, write a type guard function isPremiumUser(user: User): user is PremiumUser that checks if the user has the 'premium' role.

Loading editor…
Show hint

Define the signature as function isPremiumUser(user: User): user is PremiumUser and compare user.role with 'premium'.

Solution available after 3 attempts

Exercise 3: Custom Error Object Validation

Exercise#ts.m5.l1.e3
Attempts: 0Loading…

Implement the isCustomError(err: unknown): err is CustomError function that verifies if an unknown value is a non-null CustomError object containing the 'code' key.

Loading editor…
Show hint

First check if typeof err is 'object' and not null, then use the 'in' operator to check for the 'code' property.

Solution available after 3 attempts

Exercise 4: Admin Type Guard

Exercise#ts.m5.l1.e4
Attempts: 0Loading…

Given two interfaces User (with property role: string) and Admin (extending User and having a property adminToken: string), write a type guard function named isAdmin that accepts a user of type User and returns a type predicate user is Admin. The guard must check if the user's role is equal to 'admin'.

Loading editor…
Show hint

Define the signature as function isAdmin(user: User): user is Admin and check if user.role === 'admin'.

Solution available after 3 attempts