Check out a free preview of the full TypeScript 5+ Fundamentals, v4 course:
The "Array Types, Tuples & readonly" Lesson is part of the full, TypeScript 5+ Fundamentals, v4 course featured in this preview video. Here's what you'd learn in this lesson:

Mike discusses how TypeScript infers the type of array elements and how to define and use tuples. Using the `readonly` keyword to create immutable tuples and a discussion regarding the trade-offs of using tuples are also covered in this segment.

Get Unlimited Access Now

Transcript from the "Array Types, Tuples & readonly" Lesson

[00:00:00]
>> Let's move on to Array Types. So you're familiar, hopefully, with JavaScript arrays. So there's fileExtensions, and you can see that we've got, wait, what's going on here? That is not valid code, that should have stayed a comment. fileExtensions, we've got js and ts in an array. We can see that Typescript has inferred that this is string and then a pair of square brackets after it.

[00:00:26] That is how you describe or a preferred way to describe arrays in TypeScript. There's also another way you could do this. I would encourage you not to do this, because if you're a React developer, those little greater than, less than signs, they will clash with your JSX and cause weird things to happen.

[00:00:56] So this is really the preferred way to type arrays. And you can see we have inference there working for us as is. This works for more complicated objects, too. So here's our car object with make, model, and year. And we've got a type here that it's the same as before, we're just following it with these square brackets at the end.

[00:01:20] So it's all you have to do for array types. And what do we mean by arrays? They're arbitrary length. We can pop things out of them until they're empty, we can fill them up with a thousand objects. That is the meaning of an array in this context. May seem strange that I'm defining it that way, but the next topic we're gonna talk about is arrays of fixed length, or tuples.

[00:01:47] So you may have seen this in other programming languages. Some programming languages have a formalized tuple type that looks very different from arrays in terms of the syntax that you use. Because ultimately we have to create code that runs as JavaScript, we're kind of piggybacking on the JavaScript array type.

[00:02:10] Let's look at an example of a tuple here. So sorry, I'm gonna call this myCar2 just to not collide with the previous myCar. So here we've got an array, or here is an array containing three elements, a year, a make, and a model. So it's another way of representing a car, but we kind of have an implicit convention here, where we're saying the first element is gonna be a year.

[00:02:36] The second element is gonna be make, the third element is gonna be model. And maybe we can pass this around and we know from some contextual cue that this is what this means. This is a car, and we know how to take this information. It's almost a row of a CSV or a spreadsheet or something.

[00:02:54] If the column names, you can make sense of that. And you can see here, we can take myCar2, and we can destructure it, but we run into a problem. String or number, string or number, string or number, those are the types of all three of those values. And what's happened here is TypeScript has made the safest assumption it can without getting in our way.

[00:03:18] And it's more common to use arrays of arbitrary length. So you're adding things to them and you're removing things from them, and things at certain positions in the array have special meaning. That's usually not the case, right? So we need to do some casting here, and there were some questions in the chat earlier, what is the valid use of casting?

[00:03:40] Is it always dangerous? Well, this is a great example of where casting helps quite a bit. So here we can see part of the problem is, We can keep adding stuff to this array or we can create. Effectively, we don't have a convention that TypeScript is helping us with here.

[00:04:03] All it's doing is enforcing that the contents of this array, every member is either a string or number, but we can add a bunch of junk here and TypeScript is not gonna help us. So, We can either cast or we can use an explicit type annotation like this.

[00:04:28] So instead of doing something like this, which is how you type an array of arbitrary length, with a tuple, we're gonna put stuff in between the square brackets. And we're saying it is specifically number, string, string, 2002 Toyota Corolla. And let's look at what happens here. So here we're getting type checking helping us out.

[00:04:59] The year is supposed to be the first element, and it's not here. So it's saying string is not assignable to number. This is supposed to be a string. And then down here, we've got too many elements. We're trying to give it 4, but it only wants 3. So these are useful, especially I love to use these as function return values, where maybe you have a successful case or an error case.

[00:05:29] I use tuples of length 2 all the time, where I kind of will have some indication of success or failure and then information about the success. The string error, and then here's an error that needs to be thrown, something like that. So we do get some value in type checking.

[00:05:49] Here, we're told we don't have the correct number of elements, so that's good, but there's some interesting stuff that starts to happen. So let's look at this. This is an array literal, right, because it's a value we're creating, containing three elements. .length is number, as it should be, right, cuz we could push stuff into this array.

[00:06:11] If only we had a variable, where we had a handle on it, we could add stuff to it. But let's look at this tuple, numPair.length. it's of length 2. So TypeScript knows this is a tuple, the length is always 2, it's specifically the number 2. This is a literal type popping up again.

[00:06:28] It's the set of values that are 2. It's got one thing in there, the number 2. Now, what happens, though, is we still find that because underneath the tuple is a JavaScript array. We're not getting any safety when it comes to pushing and popping. Here, we're adding 6, and so we get 4, 5, 6, and then we pop, and then we pop, and then we pop.

[00:06:54] And check this out, the length, it's still saying it's of type 2, but we know this is effectively empty at this point cuz we've popped everything out of it. So it's kind of tuples are nice, but we haven't really seen a whole lot of safety here yet, aside from the original assignment, right?

[00:07:16] However, There's a keyword you can use here called readonly. And what this does is it tells TypeScript that it should treat this as an immutable array. So here, if we hover over the type, you can see it's a readonly number, number. The length, well, we expect it to be 2.

[00:07:42] It's still 2, but look what happens here. Push and pop, they don't exist, we're not allowed to mess with these things anymore. So the readonly keyword, when used with tuples, is pretty cool. You get very close to a degree of safety, where you can rely on these things being the shape they were when you generated them.

[00:08:07] What would you do if you wanted to guarantee a fixed size array like a tuple but want to change the values? Tuples inherently allow you to do that. Let's see how readonly works there. Yeah, it prevents you from doing that. So it is a bit of a trade off.

[00:08:38] What I would say is, generally, if you're dealing with immutable data structures like this, you will end up just creating a new tuple that has the rest of an existing tuple. To change the first element, you'd probably set a brand new first element and then spread the old tuple.

[00:08:59] Destructure the old first element out, take the rest, and spread it into the new tuple, something like that. But yeah, that is a consequence of working with a readonly array. Abhishek asks, how do we create a two-dimensional array? It would just be like this, same as you would imagine.

[00:09:33] There's twod, threed, fourd, fived into the multiverse we go. Next, we're gonna talk about structural versus nominal type systems. We've seen enough of TypeScripts so far to sort of develop an intuition for how TypeScript works. And I wanna compare this to other programming languages and other type systems you might see in the wild so you can understand how TypeScript is a little bit different.

[00:09:58] This is an important part of building that mental model we talked about, where knowing what TypeScript means when it's checking to see whether two values are compatible. We wanna understand how TypeScript does that.