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.
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.
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.
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.
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.
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.
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.
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.
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.
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 ]
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.
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.
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.
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
- Keep Tuples Short: Two or three elements are easy to read; for more values, an object with named properties is clearer.
- Label the Elements: Use labeled elements such as
[start: number, end: number]when the meaning of positions is not obvious. - Prefer Readonly Tuples: Use
readonlyoras constso that the length and the values cannot change unexpectedly. - Annotate Tuple Types: Array literals are inferred as arrays, not tuples; write the tuple type or return it from a typed function.
- Destructure at Use: Give the elements meaningful names with destructuring instead of using indexes throughout the code.
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
List all TypeScript tutorials.