Check out a free preview of the full Making TypeScript Stick course
The "Beginner Type Challenges" Lesson is part of the full, Making TypeScript Stick course featured in this preview video. Here's what you'd learn in this lesson:
Mike walks through a few examples of beginner-level exercises from the type-challenges GitHub repo and answers a student's question regarding why a read-only array and array are treated differently. Examples in this segment include implementing the correct types for each of the following If<C, T, F>, LengthOfTuple<T>, EndsWith<A, B>, and Concat<A, B>.
Transcript from the "Beginner Type Challenges" Lesson
[00:00:00]
>> This is one of my favorite ways to stay sharp and practice my use of TypeScript. There's a project fairly popular one called type challenges. So on GitHub, its type challenges is the org and the repo name. And you can see it's pretty popular, almost 15,000 stars. Effectively this is a place where you can try to solve interesting problems using only TypeScripts types.
[00:00:31]
For example, we could all probably take a string and parse CSV values so that we get an array of things between the commas, but could you take a string literal type, and then DCSV it using no runnable JavaScript, only the type itself? That's the kind of thing that you would do here, they have them sorted roughly easy, medium and hard.
[00:01:00]
But I do these regularly especially as new language features are introduced. Sometimes these get a lot easier as you get more tools that you can make use of. I will warn you, the difficulty level here is subjective. I find some of the medium ones to be quit hard and some of them were trivial, so don't get too frustrated if you feel like you are struggling with the medium it may be more appropriately categorized as a hard.
[00:01:31]
So I picked a couple that are good places for us to start. We're gonna solve these collectively as a class, but I'll keep us moving. And we can do this right in the TypeScript playground. So I've got round 1, 2, and 3. They roughly correspond to some of the most approachable things and then some things that get a little more interesting.
[00:01:52]
And then round 3 is quite challenging. And this represents only up into the medium level of what this type challenges repo allows us to practice with. So if you want more of this, if you enjoy what we're about to do, go to the GitHub repo, you can actually open pull requests and I think a bot will come by and try to mark whether you've gotten it right or not.
[00:02:17]
So you can see all these thing merge pull requests or maybe their issues, yeah, that's probably it. All these 5,000 open issues, these represent people who've submitted a possible solution. All right, so with that round 1. And I'm gonna walk us through this first one so that we can see kind of how this works, how we should think about it.
[00:02:47]
So our task is to implement a type where if it evaluates to T, to a type parameter T, if the type C, like C for condition is true, or F if the condition is false. So what we could do, I just click the try button and we ended up here.
[00:03:10]
So you can think of this up here, everything above this cut line. This is setup code, you don't need to worry about that, just scroll away from it. Down here are the test cases and these are assertions written purely using type information. So, We see here, we have the type and our job is to replace this never with something more meaningful.
[00:03:36]
So here we could say, all right, so if C extends true, true, otherwise false. And all the red squiggles are gone, and it passes. So all these little challenges you can do right in the TypeScript playground. This is true for the type challenges repo as well, this is something purely can do in your browser.
[00:04:04]
And, these assertions work as you would hope, right? So here's, this is the type and then this is the value you expect to get out of it. So in this case, because of false, we hope we're getting 42. And here cuz of true, we hope we're getting Apple.
[00:04:23]
Does that make sense how this works to everyone? Okay, so that's a freebie cuz we have a language feature, conditional type that kind of makes that one trivial. The key here is extend, we're starting to see that's almost like a, in the type world it's like a triple equal sign or something like that.
[00:04:38]
It's almost like a comparator, right? If C is, if it is a true, a thing that is true. All right, what's the next one? LengthOfTuple. So what we want here is to implement a type starting out as a never cuz that's sort of our blank to fill in.
[00:04:58]
We have a tuple called Fruits and it contains two things. The string literal type cherry and string literal type banana. In this case it also has the value there, but you can see here the type expects exactly those two strings. So the question is, how can we get to a point where we could say the length of tuple for this tuple of length 3, is 3?
[00:05:26]
And for this one, I'm using not equal, right, to make sure I have a negative test case. So the length of a tuple that's 3 long should not equal 2. Right here I'm testing fruits, which is this up here that has length 2, I hope it's 2. So, Here's what we would do here.
[00:05:56]
So we have this T and T is a tuple, we hope it's a tuple. What we could do here is an indexed access type. So we could do something like this. You could say, all right, T, it had better be an array, all right? In fact, we don't even have to do it here.
[00:06:22]
We could say if T is an array ish thing, using the same concept that we use before where we were saying, is this condition true? Extends is conceptually like, equal equal, right? Then, Look at that. As soon as I open up the square brackets here, I've got prototype things coming off of array, right?
[00:06:55]
These are things that you find on an array that begin with L or have an L in them. So there's some sense that in this branch of the type expression I'm dealing with an array ish thing. Otherwise we're not an array at all, so this is sort of where we just wanna basically give up and say you're misusing this and we can emit a never hear.
[00:07:19]
What's happening here? LengthOfTuple says false does not satisfy the constraint true. So this is where [LAUGH] debugging TypeScript code, at least this kind of TypeScript code is a lot more challenging than JavaScript because all we see is a failure. It's sort of instead of thinking of it like lines of code where you're executing them one by one and you can see this statement worked, this statement worked, this statement worked, this one failed, right?
[00:07:50]
Messing with types like this to me feels like I'm dealing with a balloon and I squeeze one side and the other side inflates and all you get is sort of a snapshot at time that like, something's not happy in some place there's no sort of order of execution here.
[00:08:03]
So I do know though I should look at the type of fruits carefully because that's the only test case here that's failing. And if I look here, anyone notice something interesting, something that's present in no other test case?
>> As const?
>> As const, okay, that for sure but what about the tooltip?
[00:08:23]
And it has to do with as constant.
>> Read only.
>> Read only, I heard Read only, that is it. So we can do this and that'll make the test case happy. We accept arrays even read only arrays, a special type of array, great. So that's how we can create a type that will give us the length of a tuple.
[00:08:46]
So if we did, like, LengthOfTuple, number, number, boolean Look at that type, it's three. So we're solving these problems just using type information, there's no running JavaScript here. This is, I know it's a little bit uncomfortable to think this way. But this is where you're picking up those heavier weights and where you're starting to do things that are deliberately more abstract, more challenging than what's in your day to day TypeScript programming work.
[00:09:32]
We want that to be easy for you and for you to be able to think about what you're trying to accomplish.
>> With a read only array, an array be treated differently when we're calling building [INAUDIBLE] method and the type?
>> That's a very good question. Effectively, why do we need to specialize, to state that read only is necessary here?
[00:09:57]
Why wasn't this working before and why is this working now? I would argue that question comes down to which is more specific versus more general? Is a writable array a subset of read only arrays or is a read only array a subset of writable arrays? And in this case a read only array is the more basic class.
[00:10:25]
And I can't really tell you why because the API surface is the same for both, because read only is not a concept that exists in JavaScript. That read only word is something that compiles away. All I can tell you is that all regular arrays can be treated as read only arrays, but read only arrays can't always be treated as writeable arrays.
[00:10:54]
So read only is the more conservative thing to be passing around, right? You can pass it to something and effectively that's something is promising they will not write to this thing. Good question. Okay, let's look at another one. I find these fun, but maybe I'm weird. All right, EndsWith.
[00:11:19]
So, okay, this is a good one for for the class cuz I think we've actually seen this one here today. You may have written it today in your IDE. So we want a type that evaluates to true if type A ends with type B. So if type A end with type B, ends with or will be like true, otherwise it'll be false.
[00:11:49]
So ice cream ends with cream, ice cream does not end with chocolate. So these are our test cases that we hope to make pass. So somebody help me out. What can I replace this with such that we get our test cases to start passing?
>> B extends and then some sort of string literal with A.
[00:12:15]
>> Be a little more specific.
>> Well, that's, I'll leave someone else [INAUDIBLE].
>> I'm just gonna do this. I think that's what you told me, right?
>> Yeah.
>> Okay, now extends, that's always used in that conditional type. That's the only place that word shows up aside from a heritage clauses, right?
[00:12:44]
Interfaces extend from other interface, but this extends, surely we're gonna have this coming after it and we can just say true and false here. We'll shuffle these around as need be. Let's see. Michael S and Oscar, have something that we should try. Oscar, the syntax is not quite correct.
[00:13:12]
I don't see any backticks, but that might be a limitation of the chat as well or we can't have backtick strings. So I'm gonna try what I see in the chat over what I take it to mean. So we could do this. Yet chat chat is erasing the backticks that's fine.
[00:13:34]
Okay, so interestingly our second test case is passing but that could mean that this evaluates to false for just about everything. So I'm gonna try to address this problem. A is not assignable to and then it gives me a list of primitive types. And does anyone have a hypothesis for why are we seeing this error message here?
[00:13:56]
Of course A is not assignable to these things it's an any, could be anything. This is wide open, right, could be absolutely anything. But like, It seems that because we're using A in this place, Something weird is going on. So it turns out in this situation these primitive types are the things that you can coerce easily to string.
[00:14:28]
So we can add a little constraint here, Like that. Maybe that'll work. Let's see, what's going on here? False does not satisfy the constraint true. Let me make sure I read this correctly. So I think we just have something reversed here, I think we were supposed to solve A, right, this is the first thing A has a B at the end.
[00:15:01]
Let me reverse these type params real quick, that could be the thing. And then we're gonna need to raise, we'll make these both extend string. Oops, cannot do my normal hotkeys there. And there we go. So now we have an ends with thing, and again, there's no, this compiles to this over here compiles to us tricked, there's no runnable JavaScript here.
[00:15:30]
We're solving these problems using only types. And in doing so we kind of learn a little bit about, hey, how does this template literal type work and the fact that we can use it any already could have used a string here, right, the set of all possible strings, also fine.
[00:15:44]
All right, so the idea here is we have A and B both are tuple types. As before, we can always add a constraint to these to make sure that our tests cases pass. But we want something that effectively zips these things together or concatenates them together. Shouldn't use zip because that means interweaving.
[00:16:09]
Here's a great test case to look at. We've got 18, 19, 20 and 21, initially as two separate tuples and we ended up with one big tuple type of them smushed together. So, Let's see if we can solve that. And I'll help us out first, Make sure these are arrayish.
[00:16:37]
Right, cuz that's the only reasonable input that's, I would, as long as it does a type error I don't care if it's violating the generic constraints or evaluating to a failing result.
>> Can you just destructure both of them?
>> I like where your head's at. Talk to me about how I would write that out.
[00:17:03]
>> Dot, dot dot A comma, dot, dot, dot B.
>> Thanks, that's it. That's all we have to do. So, I'm glad, have you ever done this before?
>> No.
>> In types, but your intuition is leading you in the right place. Because I think the effort was made here to try to make it feel like it's just what you would do for values, right?
[00:17:30]
Spread operator kind of works. Cool, well that's the last easy one.
Learn Straight from the Experts Who Shape the Modern Web
- In-depth Courses
- Industry Leading Experts
- Learning Paths
- Live Interactive Workshops