warning:this site is a work in progress, so some things are broken

Labeled tuples

3 min read
leia em português

Have you ever come across a tuple in TypeScript and couldn't tell what each element meant? In this article, we'll see how labeled tuples can make your code more readable and expressive.

What are tuples?

Tuples are a data structure similar to arrays, with one fundamental difference: they have a fixed number of elements. string[] represents an array with any number of strings.

type type StringArray = string[]StringArray = string[];
 
const const users: StringArrayusers: type StringArray = string[]StringArray = [
    'it does not matter',
    'how many strings are in here',
    'as long as they are strings, fine',
    'the array could even be empty'
];

Whereas [string, string] represents a tuple with exactly two strings: one in the first position and one in the second.

type type StringTuple = [string, string]StringTuple = [string, string];
 
const const users: StringTupleusers: type StringTuple = [string, string]StringTuple = [
    'here we need exactly two strings',
    'nothing more, nothing less'
];

Arrays are more permissive and the order of the elements doesn't matter. Tuples are the opposite: the order is fixed and must be respected.

How would you define a list where the first element is a string and the second is a number? With arrays, you could write something like (string | number)[]or Array<string | number>:

type type UserInfo = (string | number)[]UserInfo = interface Array<T>Array<string | number>;
 
const const userInfo: UserInfouserInfo: type UserInfo = (string | number)[]UserInfo = ['alves', 21];

The problem is that this allows any number of strings and numbers in the array, and their positions aren't fixed: something like  [21, '21'] would be allowed. For this case, what you need is a tuple:

type type UserInfo = [string, number]UserInfo = [string, number];
 
const userInfo: type UserInfo = [string, number]UserInfo = ['alves', 21, 'oops'];
Type '[string, number, string]' is not assignable to type 'UserInfo'. Source has 3 element(s) but target allows only 2.
/* Error: type '[string, number, string]' is not assignable to type 'UserInfo'. Source has 3 element(s) but target allows only 2. */
type type UserInfo = [string, number]UserInfo = [string, number];
 
const const userInfo: UserInfouserInfo: type UserInfo = [string, number]UserInfo = [21, 'alves']; 
Type 'string' is not assignable to type 'number'.
Type 'number' is not assignable to type 'string'.
/* Error: type number is not assignable to type string. Error: type string is not assignable to type number. */

The problem with anonymous tuples

The big problem with anonymous tuples like [string, number] is the lack of clarity. When you run into one in code, you might wonder:

  • What does the first string represent?

  • And the number? Is it an ID? An age? A timestamp?

It works for the compiler, but not always for the humans reading the code. That's where  labeled tuples shine: they bring context right into the type definition.

Labeled tuples

Labeled (or named) tuples are a feature introduced in TypeScript 4.0. With them, we can name a tuple's elements, making the code more descriptive and easier to understand.

The syntax is simple: put a name before the element's type, followed by a colon:

type type UserInfo = [userName: string, age: number]UserInfo = [stringuserName: string, numberage: number];

We can also use this in functions whose parameters are a spread tuple. Check out in this playground how the parameters of our createUser function look without labeled elements, and how they look with them.

And in case you were wondering: yes, we can also label a tuple's rest elements. You might expect the syntax to be something like  rest: ...type[], but it's actually this:

type type UserInfo = [userName: string, age: number, ...rest: unknown[]]UserInfo = [stringuserName: string, numberage: number, ...rest: unknown[]];

Final thoughts

One important detail to remember: labeled tuples exist for documentation. Beyond what I covered here, they don't change how tuples behave compared to unlabeled ones.

Another important point: when labeled tuples arrived in version 4.0, a tuple's elements had to be either all anonymous (unlabeled) or all labeled. That's no longer true: since  version 5.2 we can mix both, and you can see how it was implemented  in this pull request.

Labeled tuples are a powerful tool for making your code more readable. Use them wisely, and see you next time :)