ZetCode

TypeScript Tuples

last modified October 15, 2026

A tuple is an array with a fixed number of elements, where the type of each position is known. The type [string, number] describes an array whose first element is a string and whose second element is a number. An ordinary array type such as (string | number)[] allows any number of elements in any order. At runtime, a tuple is a plain JavaScript array; the extra information exists only for the compiler.

Basic Tuple Syntax

A tuple type is written as a list of element types in square brackets.

basic_tuple.ts
const user: [string, number] = ["Alice", 25];

console.log(user[0]);
console.log(user[1]);
console.log(user.length);

The user tuple contains a name and an age. TypeScript checks both the number of elements and their order: [25, "Alice"] or ["Alice"] would be compile-time errors. The length property has the literal type 2.

$ npx tsx basic_tuple.ts
Alice
25
2

Accessing Tuple Elements

Elements are accessed by index, like in arrays. The type of each element depends on its position.

access_tuple.ts
const point: [number, number] = [10, 20];
const entry: [string, boolean] = ["darkMode", true];

const x = point[0];
const y = point[1];

console.log(`X: ${x}, Y: ${y}, distance: ${Math.hypot(x, y).toFixed(2)}`);
console.log(`${entry[0].toUpperCase()} is ${entry[1] ? "on" : "off"}`);

TypeScript infers x and y as number, entry[0] as string, and entry[1] as boolean, so each can be used with the methods and operators of its own type.

$ npx tsx access_tuple.ts
X: 10, Y: 20, distance: 22.36
DARKMODE is on

Out-of-Range Access

Because the length of a tuple is known, the compiler rejects indexes that do not exist.

tuple_bounds.ts
const point: [number, number] = [10, 20];

console.log(point[2]);

With a plain number[] array, point[2] would compile and evaluate to undefined at runtime.

$ npx tsc --noEmit
tuple_bounds.ts(3,19): error TS2493: Tuple type '[number, number]' of length '2' has no element at index '2'.

Modifying Tuple Elements

Elements of a tuple can be reassigned, as long as the new value has the type of that position.

modify_tuple.ts
const person: [string, number] = ["Bob", 30];

person[1] = 35;
console.log(person);

person.push(40);
console.log(person, person.length);

Assigning a string to person[1] would be an error. The second part of the example shows a known gap in tuple checking: methods such as push are still available, and TypeScript allows them, so the tuple now has three elements although its type says two. Readonly tuples, shown later, prevent this.

$ npx tsx modify_tuple.ts
[ 'Bob', 35 ]
[ 'Bob', 35, 40 ] 3

Tuple with Optional Elements

A question mark after an element type makes the element optional. Optional elements must come after the required ones.

optional_tuple.ts
type Employee = [string, number, string?];

const john: Employee = ["John", 40];
const jane: Employee = ["Jane", 35, "Manager"];

for (const [name, age, role] of [john, jane]) {
    console.log(`${name} (${age}): ${role ?? "no role"}`);
}

console.log(john.length, jane.length);

The third element has the type string | undefined, so it must be handled before use; here the ?? operator provides a default text. The length of an Employee has the type 2 | 3.

$ npx tsx optional_tuple.ts
John (40): no role
Jane (35): Manager
2 3

Tuple with Rest Elements

A rest element ...T[] allows any number of elements of the given type at that position. It can stand at the end, at the beginning, or in the middle of a tuple.

rest_tuple.ts
type Scores = [string, ...number[]];
type Path = [...string[], number];

const math: Scores = ["Math", 85, 90, 95];
const art: Scores = ["Art"];

function average([subject, ...values]: Scores): string {
    const sum = values.reduce((acc, v) => acc + v, 0);
    const avg = values.length > 0 ? sum / values.length : 0;
    return `${subject}: ${avg.toFixed(1)}`;
}

console.log(average(math));
console.log(average(art));

const route: Path = ["home", "docs", "tuples", 3];
console.log(`page ${route[route.length - 1]} of ${route.slice(0, -1).join("/")}`);

A Scores tuple starts with a subject name followed by zero or more numbers. In the parameter of average, destructuring with ...values collects the numbers into a number[]. The Path type has the rest element first: any number of strings followed by one number.

$ npx tsx rest_tuple.ts
Math: 90.0
Art: 0.0
page 3 of home/docs/tuples

Labeled Tuple Elements

Elements can be given names. The labels do not change the type, but they document the meaning of each position and are shown by editors.

labeled_tuple.ts
type RangeSpec = [start: number, end: number, step?: number];

function expand([start, end, step = 1]: RangeSpec): number[] {
    const result: number[] = [];

    for (let i = start; i <= end; i += step) {
        result.push(i);
    }

    return result;
}

console.log(expand([1, 5]));
console.log(expand([0, 10, 5]));

Without labels, the type [number, number, number?] says nothing about which number is which. Labels can be combined with optional and rest elements; here step is optional and the destructuring gives it a default value of 1.

$ npx tsx labeled_tuple.ts
[ 1, 2, 3, 4, 5 ]
[ 0, 5, 10 ]

Tuple as Function Return Type

A function can return several values packed in a tuple. The caller unpacks them with destructuring.

return_tuple.ts
function divide(a: number, b: number): [quotient: number, remainder: number] {
    return [Math.floor(a / b), a % b];
}

function useCounter(initial: number): [() => number, () => void] {
    let count = initial;
    const get = () => count;
    const increment = () => {
        count++;
    };
    return [get, increment];
}

const [q, r] = divide(17, 5);
console.log(`17 / 5 = ${q} remainder ${r}`);

const [getCount, increment] = useCounter(10);
increment();
increment();
console.log(getCount());

Returning a tuple is convenient when the values belong together and the caller is expected to name them, as with q and r. The useCounter function follows the same pattern as the useState hook in React: the caller chooses the names of the getter and the function that changes the state.

$ npx tsx return_tuple.ts
17 / 5 = 3 remainder 2
12

Readonly Tuples

The readonly modifier makes all elements of a tuple read-only and removes methods that change the array, such as push.

readonly_tuple.ts
const config: readonly [host: string, port: number] = ["localhost", 8080];

const [host, port] = config;
console.log(`${host}:${port}`);

const moved = [config[0], config[1] + 1] as const;
console.log(moved);

Reading and destructuring a readonly tuple work as usual. To change a value, we create a new tuple instead of modifying the existing one. The as const assertion creates a readonly tuple from an array literal.

$ npx tsx readonly_tuple.ts
localhost:8080
[ 'localhost', 8081 ]
readonly_error.ts
const config: readonly [string, number] = ["localhost", 8080];

config[1] = 3000;
config.push(1);

Both the assignment and the call to push are rejected. A readonly tuple is the safe choice for values that should not change after creation.

$ npx tsc --noEmit
readonly_error.ts(3,8): error TS2540: Cannot assign to '1' because it is a read-only property.
readonly_error.ts(4,8): error TS2339: Property 'push' does not exist on type 'readonly [string, number]'.

Tuple Inference

TypeScript does not infer tuple types from array literals. Without an annotation, an array literal becomes an array of a union type.

mixed_tuple.ts
const inferred = ["Alice", 25, true];
const annotated: [string, number, boolean] = ["Alice", 25, true];
const constant = ["Alice", 25, true] as const;

inferred.push("extra");

console.log(inferred.length, annotated.length, constant.length);
console.log(typeof annotated[1], annotated[1] + 1);

The type of inferred is (string | number | boolean)[]: any number of elements, each of which can be any of the three types, so inferred[1] + 1 would not compile. The annotated variable has the tuple type, and constant has the type readonly ["Alice", 25, true]. A tuple type must therefore be written explicitly, come from a function return type, or be created with as const.

$ npx tsx mixed_tuple.ts
4 3 3
number 26

Tuple with Destructuring

Destructuring assigns the elements of a tuple to separate variables. It can also skip elements and swap values.

destructure_tuple.ts
const point: [number, number, number] = [10, 20, 30];

const [x, y, z] = point;
console.log(`X: ${x}, Y: ${y}, Z: ${z}`);

const [, second] = point;
console.log(second);

let a = 1;
let b = 2;
[a, b] = [b, a];
console.log(a, b);

Each variable gets the type of its position. A missing name between commas skips an element, so second is 20. The last part swaps two variables without a temporary variable: the right side creates a new tuple, which is then destructured into a and b.

$ npx tsx destructure_tuple.ts
X: 10, Y: 20, Z: 30
20
2 1

Tuples as Function Parameters

A tuple type can describe the parameter list of a function. The spread operator passes the elements of a tuple as separate arguments.

spread_args.ts
function createUser(name: string, age: number, admin: boolean): string {
    return `${name}, ${age}${admin ? ", admin" : ""}`;
}

const args: [string, number, boolean] = ["Alice", 30, true];
console.log(createUser(...args));

type UserArgs = Parameters<typeof createUser>;

const queue: UserArgs[] = [
    ["Bob", 25, false],
    ["Carol", 41, true],
];

for (const params of queue) {
    console.log(createUser(...params));
}

Spreading a tuple works because the compiler knows the type and position of every element; spreading a plain string[] into this function would be an error. The Parameters utility type returns the parameter list of a function as a tuple, here [name: string, age: number, admin: boolean].

$ npx tsx spread_args.ts
Alice, 30, admin
Bob, 25
Carol, 41, admin

Best Practices

Source

TypeScript Handbook: Tuple Types

This tutorial covered TypeScript tuples. Use them for small, fixed groups of values where the position has a clear meaning, and objects for everything else.

Author

My name is Jan Bodnar, and I am a passionate programmer with extensive programming experience. I have been writing programming articles since 2007. To date, I have authored over 1,400 articles and 8 e-books. I possess more than ten years of experience in teaching programming.

List all TypeScript tutorials.