2 min read

TypeScript Pattern 101: Type Guards

Type guards in TypeScript function as safety checks. They help ensure you’re dealing with the exact type you expect, making them invaluable for dealing with complex data structures or various types.
TypeScript Pattern 101: Type Guards

Unlocking Safer, More Robust Code Through Advanced Type Checking


Introduction

Type guards in TypeScript function as safety checks. They help ensure you’re dealing with the exact type you expect, making them invaluable for dealing with complex data structures or various types.

User-Defined Type Guards

Create custom type guards using functions that return type confirmations based on the is keyword.

function isHotel(place: unknown): place is Hotel { 
    return (place as Hotel).propertyType === 'Hotel'; 
} 
 
interface Hotel { 
    propertyType: 'Hotel'; 
    rooms: number; 
    amenities: string[]; 
} 
 
const accommodation: unknown = { propertyType: 'Hotel', rooms: 120, amenities: ['pool', 'gym'] }; 
 
if (isHotel(accommodation)) { 
    console.log(`This hotel has ${accommodation.rooms} rooms.`); 
}

Generics in Type Guards

Sometimes you might want a more flexible type guard, one that works across various types. Generics come in handy here.

function isType<T>(val: unknown, check: (arg: unknown) => boolean): val is T { 
    return check(val); 
} 
 
const isNumber = (val: unknown): val is number => typeof val === 'number'; 
 
if (isType<number>(42, isNumber)) { 
    console.log("It's a number!"); 
}

Discriminated Unions

Consider the example of a booking system that features various types of accommodations, such as hotels, hostels, or Airbnb listings.

interface Hotel { 
    kind: 'hotel'; 
    rooms: number; 
} 
 
interface Hostel { 
    kind: 'hostel'; 
    beds: number; 
} 
 
interface Airbnb { 
    kind: 'airbnb'; 
    bedrooms: number; 
} 
 
type Accommodation = Hotel | Hostel | Airbnb; 
 
function getCapacity(accommodation: Accommodation): number { 
    switch (accommodation.kind) { 
        case 'hotel': 
            return accommodation.rooms; 
        case 'hostel': 
            return accommodation.beds; 
        case 'airbnb': 
            return accommodation.bedrooms; 
        default: 
            const _exhaustiveCheck: never = accommodation; 
            return _exhaustiveCheck; 
    } 
}

Extended Discriminated Unions

In a more complicated scenario, let’s consider different amenities offered by Hotels, Hostels, and Airbnb listings. Discriminated Unions can be extremely helpful to handle such diversity.

interface Hotel { 
  kind: 'hotel'; 
  rooms: number; 
  gym: boolean; 
} 
 
interface Hostel { 
  kind: 'hostel'; 
  beds: number; 
  communalKitchen: boolean; 
} 
 
interface Airbnb { 
  kind: 'airbnb'; 
  rooms: number; 
  entirePlace: boolean; 
} 
 
type Accommodation = Hotel | Hostel | Airbnb; 
 
function listAmenities(accommodation: Accommodation): string[] { 
  switch (accommodation.kind) { 
      case 'hotel': 
          return [`Rooms: ${accommodation.rooms}`, `Gym: ${accommodation.gym ? 'Yes' : 'No'}`]; 
      case 'hostel': 
          return [`Beds: ${accommodation.beds}`, `Communal Kitchen: ${accommodation.communalKitchen ? 'Yes' : 'No'}`]; 
      case 'airbnb': 
          return [`Rooms: ${accommodation.rooms}`, `Entire Place: ${accommodation.entirePlace ? 'Yes' : 'No'}`]; 
      default: 
          const _exhaustiveCheck: never = accommodation; 
          return _exhaustiveCheck; 
  } 
} 
 
 
listAmenities({ kind: 'hostel', 'beds': 10, communalKitchen: false }) 
 
// ERROR: Object literal may only specify known properties, and 'entirePlace' does not exist in type 'Hostel' 
listAmenities({ kind: 'hostel', 'beds': 10, entirePlace: true })

Using in and instanceof Keywords

The in keyword checks for the existence of a property within an object, while instanceof is used for identifying an object's class.

// Checking with 'in' 
if ('rooms' in accommodation) { 
    // Do something with accommodation.rooms 
} 
 
// Checking with 'instanceof' 
class BoutiqueHotel { 
    quality: string = "high"; 
} 
 
const myHotel = new BoutiqueHotel(); 
 
if (myHotel instanceof BoutiqueHotel) { 
    console.log(myHotel.quality); // Outputs "high" 
}

The Dark Side of Loose Typing

Using loose types like any in TypeScript may seem convenient, but it comes with risks, especially in complex systems like booking platforms. It makes your code harder to debug and maintain, undoing some of the benefits of using TypeScript in the first place.

Closing Thoughts

There you go. A deep dive into type guards in TypeScript. These tools offer a robust way to manage complex types in your applications. Stay tuned for our next topic; catch you then!

Type guards
Subscribe to our newsletter.

Become a subscriber receive the latest updates in your inbox.