Write TypeScript like a pro. Typescript Icon

Follow the ultimate TypeScript roadmap.

Understanding TypeScript: instanceof Type Guard

Welcome back to the TypeScript Type Guards series! You’re entering the realms of next level knowledge, woohoo!

For reference, the 4 articles in this TypeScript series:

Enjoying the series? Come and master the whole TypeScript language with us across 2 courses, beginner concepts and finally advanced. Full of real-world examples, it’ll answer so many questions you have and will have. Click here to check out more of what you’ll learn with us!

Before we dive into what the instanceof Type Guard can do for us we are going to do a quick demonstration what instanceof on its own does for us.

Table of contents

instanceof

For this what we are going to use is just a simple class and then we’ll move along to a more real-world demonstration. We create a class called Foo and we pass it an empty bar() method.

class Foo {
  bar() {}
}

What is important to understand is that this will compile down to es5 which we will see something like this:

function Foo() {}
Foo.prototype.bar = function () {};

This is how we can create our own custom objects in JavaScript and how we can extend the prototype chain so that when we create new instances they all inherit the same methods. It’s the prototype piece which is important because all the classes are sugar syntax essentially for this.

So let’s continue and say const and call it bar and then create a new instance of our Foo() class.

class Foo {
  bar() {}
}

const bar = new Foo() 

The interesting part and what this instanceof operator will do for us by itself, without Type Guards, is that it tests whether the prototype property of a constructor exists somewhere in another object.

One way we could test this is saying Object.getPrototypeOf() and because bar and the value of bar is in fact an instance of Foo() we want to ask for the prototype of bar. We can say if it equals Foo.prototype then essentially bar is going to be an instance of Foo. Where we access the .prototype we’re not creating a new instance of Foo we are just referencing that prototype object.

class Foo {
  bar() {}
}

const bar = new Foo(); 

//true
console.log(Object.getPrototypeOf(bar) === Foo);

You can see that if we log this out we get a true value and we’re essentially seeing if bar is an instance of Foo. Which in this case it’s telling us that it is. Now we do have the ability to use bar and the instanceof Foo.

class Foo {
  bar() {}
}

const bar = new Foo(); 

// true
console.log(bar instanceof Foo);
// true
console.log(Object.getPrototypeOf(bar) === Foo);

As you can see there is no difference between the two. Which means that we can either use the getPrototypeOf or what we really want to use is the instanceof keyword. Now we know what the instanceof does we can move down and implement our own example and we’ll demonstrate this with a Type Guard.

## instance and Type Guards ##

We want a class of Song, we are going to create another constructor it will have a public title property of type string, a public duration of type number. We are not going a union type in this video we are going to keep that duration as a number.

const bar = new Foo(); 

class Song {
  constructor(public title: string, public duration: number) {}
}

Now we want to create a playlist class, inside of there we are going to create a constructor with a public name property of type string and a public property called songs and this will hold an array of our Song.

const bar = new Foo(); 

class Song {
  constructor(public title: string, public duration: number) {}
}

class Playlist {
  constructor(public name: string, public songs: Song[]) {}
}

What we are interested in doing is whether we pass it a Song or whether we pass it a Playlist we just want to know what it’s called, we want the name of it. To do this we will setup a function called getItemName which will take an item or either type Song or Playlist.

Angular Directives In-Depth eBook Cover

Free eBook

Directives, simple right? Wrong! On the outside they look simple, but even skilled Angular devs haven’t grasped every concept in this eBook.

  • Green Tick Icon Observables and Async Pipe
  • Green Tick Icon Identity Checking and Performance
  • Green Tick Icon Web Components <ng-template> syntax
  • Green Tick Icon <ng-container> and Observable Composition
  • Green Tick Icon Advanced Rendering Patterns
  • Green Tick Icon Setters and Getters for Styles and Class Bindings

We might have to manually check whether item in fact has a title property. So we might actually say if item, and we use that type assertion to say at this moment in time I’m treating item as Song so I can successfully lookup that title property.

const bar = new Foo(); 

class Song {
  constructor(public title: string, public duration: number) {}
}

class Playlist {
  constructor(public name: string, public songs: Song[]) {}
}

function getItemName(item: Song | Playlist) {
  if((item as Song).title) {
  }
}

Inside our if statement we are now assuming that we are dealing with a Song. The problem is that TypeScript doesn’t know this. So what we need to do is specify it again and then we get access to the autocompletion. Now this part of our code is safety checked.

const bar = new Foo(); 

class Song {
  constructor(public title: string, public duration: number) {}
}

class Playlist {
  constructor(public name: string, public songs: Song[]) {}
}

function getItemName(item: Song | Playlist) {
  if((item as Song).title) {
    return (item as Song).title;
  }
}

Underneath we would have to do the exact same for the Playlist.

const bar = new Foo(); 

class Song {
  constructor(public title: string, public duration: number) {}
}

class Playlist {
  constructor(public name: string, public songs: Song[]) {}
}

function getItemName(item: Song | Playlist) {
  if((item as Song).title) {
    return (item as Song).title;
  }
  return (item as Playlist).name;
}

This means that we are definitely returning a string from the function because our title is a string and the name is also a string. Now let’s go ahead and say we want to access the song name and we’re going to invoke our getItemName function. We can pass in a new song and we can give it some digits.

const bar = new Foo(); 

class Song {
  constructor(public title: string, public duration: number) {}
}

class Playlist {
  constructor(public name: string, public songs: Song[]) {}
}

function getItemName(item: Song | Playlist) {
  if((item as Song).title) {
    return (item as Song).title;
  }
  return (item as Playlist).name;
}

const songName = getItemName(new Song('Wonderful Wonderful', 300000));
console.log('Song name:', songName);

Next we can access the playlist name. We are going to create a new instance of our playlist and we can supply the name of the Playlist and an array of songs.

const bar = new Foo(); 

class Song {
  constructor(public title: string, public duration: number) {}
}

class Playlist {
  constructor(public name: string, public songs: Song[]) {}
}

function getItemName(item: Song | Playlist) {
  if((item as Song).title) {
    return (item as Song).title;
  }
  return (item as Playlist).name;
}

const songName = getItemName(new Song('Wonderful Wonderful', 300000));
console.log('Song name:', songName);

const playlistName = getItemName(
new Playlist('The Best Songs', [new Song('The Man', 300000)])
);
console.log('Playlist name:', playlistName);

Now if we log this out we would get:

Song name: Wonderful, Wonderful
Playlist name: The Best Songs

So this code is working as we expect it to. However, we don’t want to keep littering our code with all this information. This doesn’t just apply to the instanceof operator, it also applies to the other Type Guards in TypeScript.

What we’re going to do is clean up our function getItemName. If we say that our item is an instance of Song then we definitely know it’s a Song. TypeScript can infer this information for us which means we can remove our type assertion. Similarly we can go ahead and do the same for the playlist.

function getItemName(item: Song | Playlist) {
  if(item instanceof Song) {
    return item.title;
  }
  return item.name;
}

What we have done is refactored our code to not use type assertions but to allow us to use that instanceof operator to infer the type. It’s going to check whether the item is an instance of the song which is just a static class and if it is it’s going to infer those types for us. This doesn’t mean that we cannot add another if statement and do the same for Playlist as we did for Song. But we can also leave it as it is.

That’s an overview of the instanceof, how it works, the prototype and how we can use them to infer types with our Type Guards.

Learn TypeScript the right way.

The most complete guide to learning TypeScript ever built.
Trusted by 82,951 students.

Todd Motto

with Todd Motto

Google Developer Expert icon Google Developer Expert

Related blogs 🚀

Free eBooks:

Angular Directives In-Depth eBook Cover

JavaScript Array Methods eBook Cover

NestJS Build a RESTful CRUD API eBook Cover