Check out a free preview of the full Intermediate TypeScript, v2 course

The "Bottom Types" Lesson is part of the full, Intermediate TypeScript, v2 course featured in this preview video. Here's what you'd learn in this lesson:

Mike discusses bottom types, which do not accept any value and represent an impossibility. An example with an unreachable error class to handle cases where unexpected values are encountered at runtime is also demonstrated in this segment.


Transcript from the "Bottom Types" Lesson

>> Let's move on to bottom types. All right, in contrast with top types, bottom types do not accept any value. They're happy with nothing, they represent an impossibility in some sense. So let's look at where we might see one of these in the wild. We have a car, and we have a truck, we've got a method called drive on car.

We've got tow on truck, this is just an example to illustrate that these are two different shapes. I've got my vehicle equals obtain random vehicle. There you go,the fact that it returns any means like we can pretend that It's giving us a value. My vehicle is still of type vehicle.

What is vehicle? It's a truck or a car. This is a union type, it's either or, could be a truck, could be a car. No base class involved here, right? It's just the union type. So let's build what we call an exhaustive conditional. And what that means is, we are trying to model the entire domain of something that we're examining, meaning we wanna look at every possibility, everything it could possibly be.

And we want some assurance that at the end, there's nothing left unhandled. So here we're gonna have a type card that says, up here it's a vehicle right? myVehicle instance of truck if that's true, it is a truck and we can call tow safely. If myVehicle is an instance of car I can say, all right my vehicles a car we can call drive safely.

And what's left? What's left in the set of possible values this could be. How do we describe that set? [INAUDIBLE]
>> There's nothing left in it. It's an empty set to set with nothing in it. My vehicle is a never and then you should never get here. You should never reach this branch of code if the types truly represent what this value is going to be at runtime.

You shouldn't get here and what I've done here is I've said I'm gonna create a value of type never, and I'm gonna assign my vehicle to it. This is gonna be good in a sec. So okay, another developer comes along and they say, hey, boats are vehicle here's a boat.

And I'm just gonna go up and I'm gonna say vehicle because it can be a boat too. And look at this. Never value is declared, no, that's the wrong error type boat is not assignable to type never. And what we're saying here is effectively, this line is an assertion that nothing should be left over in this block.

It's a way for us to say, I think I've handled every possible thing that this could be, and now there's a new thing that's left over at the end, right? This is like a pie, like a pie chart. We should have had no pieces of the pie left over by the time we reached this point, but we do and we're getting an error.

This is great, feedback that's happening right in the authoring phase where we don't have to wait for unit tests to run. We don't have to hope, that we have coverage here, it's going to fail. Now, you don't handle every kinda value this way, but when you want to handle something exhaustively.

Meaning handle every possible mode something can be in, or every kinda vehicle it can be, this lets you do this. And if we didn't wanna handle boat, we could still do this. Something like that. [BLANK_AUDIO. Why are we getting an error here? All right, let's yank this up just cuz of hoisting is not a thing anymore.

Note though that hoisting with types happened [LAUGH]. All right and let's see what's happening here I need an else. And now, this line of code that I was telling you is kind of an assertion, it's happy now. We're deliberately doing nothing in the case that it's a boat.

But that's enough to ensure that we don't end up in this block, in this code block here, in the bookcase, right? That has a specific thing, it's a no up. So we could still say, okay, we're handling this exhaustively, even though in some cases we're doing nothing about it, this is super useful.

Okay, but we can make it even more useful. Instead of just having that assertion there, we're going to create an error class or an error subclass. And the arguments that this error class takes well first we're gonna take a never as an argument. And then we're gonna take a message and what we can do here is say wanna grab this, and I'm gonna put it in our last else block.

Instead of this assertion, I'll leave that there just for illustration. Up hoisting again. I haven't found a better way to do this sequentially like this, but there you go. We would spoil the surprise, you'd see it there as we scroll down. So, now we're saying I want to throw an error and I'm calling this an unreachable error.

Cuz, we shouldn't hit this if the types are telling the truth. Nobody at runtime is slipped in a possibility that there's another thing here. Then I should throw, and if we remove this else block that elsif you can see that the unreachable error is lighting up just like that one line assertion is.

The difference here is that sometimes we lie with type, sometimes we're especially if this is like a JSON response that you get from an API or something. Sometimes it's an innum and an API or something like that. Something where it's not part of your compiled code, in this case, it's impossible to sort of sneak a boat class in if it's not part of the code like that.

You gotta have a class there, we're doing instance of checks. But in a case where it was something coming in from the outside, this would say, all right, something new popped up. The way my code is written assumes that this is never possible and now we're getting an unreachable error.

We're gonna see that in our, whatever observability tool we're using, like if it's sentry or Splunk or something else, and we'll be able to act on that. So this is great because this means, well, at build time, you get the benefit of a type equivalence check to whenever, right?

You get the same enforcement that you're exhaustively handling. This domain, the domain being like the domain of a vehicle, all things that a vehicle could be. And at runtime, you get, in the event that you actually reach this piece of code, you'll get some indication that you hit this and you need to take a look at it.

Because at the time you wrote this code, you thought that to be impossible. Something happened that you didn't think could happen, and now you're alerted to it. So I love this unreachable error concept for that reason. And of course, here, if we went back, now this is the way we would throw it away, we would deploy it.

We'd say, all right, this is never, this will type check, this will compile. Ship it and then if this ever happens you know we got to really take a look at this. So that's the concept of an exhaustive conditional

Learn Straight from the Experts Who Shape the Modern Web

  • In-depth Courses
  • Industry Leading Experts
  • Learning Paths
  • Live Interactive Workshops
Get Unlimited Access Now