Even More TypeScript Types You Need To Know

There’s still more in TypeScript to review and understand. And that’s a good thing.

David Choi
Feb 27 · 6 min read

If you learn better with video

To kick things off what is an alternative way of writing the field b?

class A {
a: number;
b?: number;
}

That’s right, use a union b: number | undefined. You get the same effect. Which leads me to my next example. What if you have something like this? Will this code compile?

interface Car {    wheels: number;    carry();}interface Truck {    wheels: number;    payload();}
function getVehicle(): Car | Truck { // pretend it randomly returns a car or truck return { wheels: 4, payload: () => { console.log( "truck running"); } }}let vehicle = getVehicle();console.log("wheels " + vehicle.wheels);vehicle.payload();

This example will not compile. Initially that seems strange. We know getVehicle will return a Truck and the call to vehicle.payload() is a call on the Truck type. So then why not? Since the return type of getVehicle returns a union of a Car or a Truck. The compiler cannot know for certain what will be returned. It’s a similar issue to unknown. Remember TypeScript is a development time compiler. Not a runtime. Since at runtime either of these types could result the TypeScript compiler has to play it conservative. So what we need to do is check what type it actually is before using it.

Type Guards To The Rescue

So the type guards in TypeScript are actually a mixture of features from JavaScript and newer features from TypeScript. Let’s go through them to see how they can help us with the issues around unions.

Type Assertions

Let’s start with Type Assertions and see how this may help us. Take a look at an update of the previous code with assertions.

interface Car {    wheels: number;    carry();}interface Truck {    wheels: number;    payload();}function getVehicle(): Car | Truck {    // pretend it randomly returns a car or truck    return {        wheels: 4,        payload: () => { console.log( "truck running"); }    }}let vehicle = getVehicle();if((vehicle as Car).carry) {    (vehicle as Car).carry();} else if((vehicle as Truck).payload) {    (vehicle as Truck).payload();}

So if you’re not familiar type assertions are a way of doing type casting in TypeScript. However I interpret them slightly differently from traditional casting, such as in C#. You might think using the as keyword would produce an undefined or null if the object is not of the given type. However it does not. You still get back an object of whatever real type the object is. So then what does using as give us? All it does is it temporarily halts the compiler on that object from erroring and allows a statement that otherwise would fail immediately, due to type uncertainty, to proceed.

TypeScript, when using unions, cannot be sure which union member you are referring to, it needs some help. If you don’t cast, then as you saw in the first code example, the compiler will not allow you to proceed as it cannot determine which of the union types you are referring to. So by using a cast you are saying something to the effect of, “Hey compiler let’s assume for a moment that this is of type X”. Thereafter you attempt to use it as you would an object of that type by calling one of its members. If the member exists, then of course the if statement is true, but if not then of course it is false. But either way we need the cast to allow the compiler a moment to stop complaining and “pretend” it is of a certain type.

Type Predicates

So we can see that type assertions can help for this scenario. But what if we don’t want to type all that code and we want a neater approach, like for example a single function. Look at this code.

interface Car {    wheels: number;    carry();}interface Truck {    wheels: number;    payload();}function getVehicle(): Car | Truck {    // pretend it randomly returns a car or truck    return {        wheels: 4,        payload: () => { console.log( "truck running"); }    }}let vehicle = getVehicle();function isTruck(vehicle: Car | Truck): vehicle is Truck {    return typeof (vehicle as Truck).payload === "function";}console.log("Is truck? ", isTruck(vehicle));

Now we have a new function called isTruck. Inside of isTruck we can see a typeof test for a function. But we can also see that the return value of this function is a bit odd, “vehicle is Truck”. This is the type predicate and it indicates that this function tests specifically for this type condition, in other words it’s a type guard. Now we could have just returned boolean based upon the typeof result. However that is not sufficiently clear since JavaScript’s typeof only deals with primitive types like string, number, boolean, object, etc. If we only return boolean the information given is that we are looking for a member called payload that is a function. That is a somewhat incorrect statement, since what we are really trying to do is determine the object type by checking for one of its members. The type predicate gives us this explicit unambiguous indication.

In Operator

Another way of doing a type guard is to use the in operator. This operator allows you to test if a field, based upon its name, is existing in an object. So one way of checking for a type could be like this.

interface Car {    wheels: number;    carry();}interface Truck {    wheels: number;    payload();}function getVehicle(): Car | Truck {    // pretend it randomly returns a car or truck    return {        wheels: 4,        payload: () => { console.log( "truck running"); }    }}let vehicle = getVehicle();function run(vehicle: Car | Truck) {    if ("payload" in vehicle) {        (vehicle as Truck).payload();    } else {        (vehicle as Car).carry();    }}run(vehicle);

Now again this way is not as explicit as a type predicate, because you’re not directly checking the type so much as checking whether the object has a member that the type has. So it is one possible method, but perhaps not the best one since it is possible for different types to have the same member name for some properties.

Assertion Function

This is not a type guard, but it can be used like one if needed. Take a look at this code.

class Car {    wheels: number;    carry() { console.log( "car running"); };}class Truck {    wheels: number;    payload() { console.log( "truck running"); }}function getVehicle(): Car | Truck {    // pretend it randomly returns a car or truck    const truck = new Truck();    truck.wheels = 4;    return truck;}let vehicle = getVehicle();function assertTruck(vehicle: Car | Truck): asserts vehicle is Truck {    if (vehicle instanceof Truck) {         console.log("vehicle is a truck!");         return;    }    throw new Error("Not a truck!");}assertTruck(vehicle);

An assertion function is a lot like a function that returns never, but more explicit about the conditions that might produce a never result. Assertion functions provide two main features. They tell the function user that an exception may occur. And they assert that a certain condition is being checked. In this case that check is that vehicle is a Truck. Note also, that the Truck and Car types are now classes and not interfaces. This is because instanceof is looking for an instance of some type, which of course interfaces cannot be.


Sweet. We’ve now reviewed the main ways to guard against incorrect or unwanted type usage. Happy coding.

Here’s my previous two stories on this topic, one and two.

I‘ve written a book on this topic, including source code, on Amazon.

David Choi

Written by

Developer at DzHaven.com, where devs help devs. Amazon at Amazon.com/author/davidchoi. Twitter at Twitter.com/jsoneaday. Youtube as David Choi.

JavaScript in Plain English

Learn the web's most important programming language.

More From Medium

More from JavaScript in Plain English

More from JavaScript in Plain English

How to Secure Your API With JSON Web Tokens

More from JavaScript in Plain English

More from JavaScript in Plain English

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade