Tuples in TypeScript
Tags: typescript, javascript, programming, beginners
Published: 2023-03-19 21:57:15

Do you know what Tuple is? If you are a JavaScript developer, I guess you are unfamiliar with this data type because there is no Tuple in JavaScript.

Tuple is a more strict version of Array, which can store only a static number of elements and types at each index.

Let me clarify the difference between Tuple and a simple Array and explain how to use it.

## Type Declaration
### Union-Type Array

When you want to use a simple union-type array, the syntax is as follows:

```typescript
type UnionArrayType = (string | number)[];

let unionArray1: UnionArrayType;
unionArray1 = ["hello world", 1]; // OK

let unionArray2: UnionArrayType;
unionArray2 = [2, "hello world"]; // OK

let unionArray3: UnionArrayType;
unionArray3 = [3, "hello", "world"]; // OK

let unionArray4: UnionArrayType;
unionArray4 = [4, "hello", "world", true];
// Type 'boolean' is not assignable to type 'string | number'.
```

The array of `UnionArrayType` can accept any number of members as long as the types of elements are string or number.

### Tuple

The syntax for Tuple is as follows:

```typescript
type MyTupleType = [string, number];

let myTuple1: MyTupleType;
myTuple1 = ["hello world", 1]; // OK

let myTuple2: MyTupleType;
myTuple2 = [2, "hello world"];
// Type 'number' is not assignable to type 'string'.
// Type 'string' is not assignable to type 'number'.

let myTuple3: MyTupleType;
myTuple3 = ["hello world", 1, 2];
// Type '[string, number, number]' is not assignable to type 'MyTupleType'.
  // Source has 3 element(s) but target allows only 2.
```

As you can see above, MyTupleType cannot accept the array whose order is “number and string”, and the array whose number of members is more than or less than two.

## What is the benefit of Tuples?

If the order of members in an array affects your program, Tuple Type will bring benefits to you. For instance, you may pass arguments to a function by using the … rest parameters syntax.

Here is a simple example.

```typescript
type PersonType = [string, number];

const checkAgeToDrink = (name: string, age: number) => {
　　const legalAgeToDrink = 20;
　　const displayName = name[0].toUpperCase() + name.slice(1);
　　const canDrink = age >= legalAgeToDrink;

　　if(canDrink) {
　　　　console.log(displayName + " can drink!");
　　} else {
　　　　console.log(displayName + " has to wait " + (legalAgeToDrink - age) + " years until they can drink.");
　　}
};

const person1: PersonType = ["john", 10];

checkAgeToDrink(...person1);
// OK [LOG]: "John has to wait 10 years until they can drink."

const person2 = ["mary", 21];

checkAgeToDrink(...person2);
// A spread argument must either have a tuple type or be passed to a rest parameter.

const person3: [number, string] = [30, "mike"];

checkAgeToDrink(...person3);
// Argument of type 'number' is not assignable to parameter of type 'string'.
```

In the example above, only `person1` can be passed as … rest parameters because `person1` variable guarantees the order of members’ type.

Speaking of `person2`, you will see the error message from the TypeScript type checker though it will run at runtime without any issues because the type of this variable, `(string | number)[]`, does not guarantee the order of its members. TypeScript type checker notices a potential bug in advance and lets developers know what might be wrong.

When it comes to `person3`'s case, it must be wrong because the order of the members’ type is wrong.

## Tuple inferences
When you see the previous example code, you might wonder what is wrong with `person2`.

Let's look into the details of the difference between `person1` and `person2`.

```typescript
const person1: PersonType = ["john", 10];
// const person1: PersonType
// type PersonType = [string, number]

const person2 = ["mary", 21];
// const person2: (string | number)[]
```

As you can see, the type of `person2` is automatically inferred as Union-Type Array while `person1` is declared with the type annotation to make it the `Tuple` type. It means you need to explicitly provide a type annotation when you want to use the Tuple type.

### Alternative way to use Tuple, Const asserted tuples

It may be annoying for some developers to write type annotations for Tuples since it is just an extra syntax.

If you feel like that, there is another option. That is the so-called “const assertion”.

By providing a const assertion operator after a value, you can make a tuple as follows:

```typescript
const checkAgeToDrink = (name: string, age: number) => {
    ......
};

const person2 = ["mary", 21] as const;

checkAgeToDrink(...person2); // OK
```

Please keep in your mind that the tuples created by a const assertion are not completely the same as a tuple created with an explicit type annotation because the const assertion gives a read-only feature to a value.

```typescript
const person2 = ["mary", 21] as const;
// const person2: readonly ["mary", 21]

person2[0] = "leo";
// Cannot assign to '0' because it is a read-only property.
```

## Summary

- There is a specific type of array that is called a “Tuple” in TypeScript
- Tuples are more strict and can store only a static number of elements and types at each index, while Union-type arrays are more flexible
- Tuple type cannot accept arrays whose order of types is different and the number of members is different.
- If the order of members in an array affects your program, Tuple Type will bring benefits to developers
- If you want to use Tuples, there are two ways of doing it.
    - Explicit annotations
    - Const assertions