DEV Community

Cover image for Tuples in TypeScript
Tomohiro Yoshida
Tomohiro Yoshida

Posted on

Tuples in TypeScript

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:

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'.
Enter fullscreen mode Exit fullscreen mode

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:

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.
Enter fullscreen mode Exit fullscreen mode

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.

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'.
Enter fullscreen mode Exit fullscreen mode

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.

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

const person2 = ["mary", 21];
// const person2: (string | number)[]
Enter fullscreen mode Exit fullscreen mode

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:

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

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

checkAgeToDrink(...person2); // OK
Enter fullscreen mode Exit fullscreen mode

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.

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.
Enter fullscreen mode Exit fullscreen mode

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

Top comments (0)