Tuples
A tuple is an array with a fixed length where each position has its own type. Where string[] says “any number of strings,” a tuple like [string, number] says “exactly a string then a number.” Tuples are ideal for coordinate pairs, key/value entries, and functions that return a small bundle of differently typed values. TypeScript adds optional elements, rest elements, named labels, and readonly variants, giving tuples real expressive power beyond plain arrays. This page covers the syntax and the cases where a tuple beats an object or a loose array.
Fixed-length ordered types
Declare a tuple by listing each element’s type in brackets. The number and order of types is enforced, so both the length and the per-position type are checked.
let pair: [string, number] = ["age", 36];
// ❌ Error: Type 'number' is not assignable to type 'string'.
pair = [36, "age"];
// ❌ Error: Source has 3 element(s) but target allows only 2.
pair = ["a", 1, 2];
The annotation [string, number] requires position 0 to be a string and position 1 to be a number, and rejects any other length. Indexing returns the precise type for that slot — pair[0] is a string and pair[1] is a number.
Optional elements
Mark trailing elements optional with ? to allow a tuple of variable length up to a maximum. Optional elements must come after all required ones.
type Point = [x: number, y: number, z?: number];
const p2: Point = [1, 2]; // ✅ 2D
const p3: Point = [1, 2, 3]; // ✅ 3D
// ❌ Error: Type 'undefined' is not assignable to type 'number'.
const bad: Point = [1];
Here z may be present or absent, so both two- and three-element forms are valid. Accessing p2[2] is typed as number | undefined, prompting you to guard before use. Required elements cannot follow an optional one.
Rest elements
A rest element ...T[] captures a variable number of trailing values of one type, letting a tuple have a fixed prefix and an open-ended tail.
type Path = [root: string, ...segments: string[]];
const route: Path = ["users", "42", "posts"];
type Pair = [first: number, ...rest: number[]];
const nums: Pair = [1, 2, 3, 4];
Path requires at least the root string and then accepts any number of additional segment strings. Rest elements can also appear in the middle ([string, ...number[], boolean]), which is how TypeScript types variadic function arguments precisely.
Named tuple elements
Add labels to tuple positions for documentation. Labels appear in editor tooltips and signatures but have no runtime effect — they are purely for readability.
type Rgb = [red: number, green: number, blue: number];
function toColor(c: Rgb): string {
const [r, g, b] = c;
return `rgb(${r}, ${g}, ${b})`;
}
toColor([255, 128, 0]);
The labels red, green, and blue make the tuple self-documenting in hovers and error messages. Once you label one element you must label them all. Labels do not change the structural type — [red: number] and [number] are assignable to each other.
Readonly tuples and as const
Prefix a tuple type with readonly to forbid element reassignment and mutating methods. An as const assertion on an array literal produces exactly such a readonly tuple of literal types.
const origin: readonly [number, number] = [0, 0];
// ❌ Error: Cannot assign to '0' because it is a read-only property.
// origin[0] = 1;
const config = ["GET", "/api"] as const;
// type: readonly ["GET", "/api"]
origin cannot be mutated through any index or method. The as const on config both freezes the tuple and narrows each element to its literal value, which is invaluable for discriminated unions and exhaustive lookups. See Const Assertions.
Labeled returns and destructuring
Tuples shine as function return types when you need to send back several values without naming an object. React’s useState is the canonical example. Destructuring at the call site names each slot.
function useCounter(start: number): [count: number, increment: () => void] {
let count = start;
return [count, () => { count += 1; }];
}
const [count, increment] = useCounter(0);
increment();
The labeled return [count: number, increment: () => void] documents both positions, and destructuring gives callers ergonomic local names. Reach for an object instead once you have more than three values or the order stops being obvious.
Tuple vs array
| Aspect | Tuple [string, number] | Array (string | number)[] |
|---|---|---|
| Length | Fixed (enforced) | Any length |
| Per-position type | Yes, position-specific | No, same union everywhere |
| Indexing type | Exact type per slot | The element union |
| Best for | Pairs, records, returns | Homogeneous collections |
| Mutating methods | Discouraged (length-breaking) | Expected |
Prefer a named tuple or a small object over a bare positional tuple in public APIs. Positional values are easy to swap by mistake; labels and object keys make intent explicit and refactors safer.
Best Practices
- Use tuples for fixed-length, positionally typed data like coordinates and entries.
- Label tuple elements (
[x: number, y: number]) to document each position. - Place optional
?and...restelements after all required elements. - Add
readonlyto tuples that should never be mutated after creation. - Use
as constto derive a literal-typed readonly tuple from an array literal. - Destructure tuple returns immediately to give the values meaningful names.
- Switch to an object once you exceed ~3 values or the ordering is unclear.