Tuple types

Type a fixed-length array position by position, with optional, rest and labelled elements.

Signature
type Pair = [string, number];
type Point = [x: number, y: number, z?: number];     // labels, optional element
type Row = [id: number, ...cells: string[]];         // rest element
type Frozen = readonly [number, number];             // readonly tuple
  • Added in TypeScript 1.3

Parameters

ParameterTypeDescription
$elements[T0, T1, ...]One type per position, in order. Indexing outside the declared positions is an error rather than a widened lookup.
$optional elementoptional[T, U?]An element marked with `?`. It may only appear after all required elements, and it makes `length` a union of the possible lengths.
$rest elementoptional[...T[]]Absorbs any number of elements of that type. A tuple with a rest element has no single `length`.
$labelsoptional[name: T]Documentation-only names shown in editor hints and error messages. If one element is labelled, all of them must be.
$readonly modifieroptionalreadonly [T, U]Forbids writing to any position. Array literals given `as const` are inferred as readonly tuples.

Errors

tsc reports "Tuple type '[string, number]' of length '2' has no element at index '2'" for out-of-range access, "Cannot assign to '0' because it is a read-only property" for writes to a readonly tuple, and "A required element cannot follow an optional element" for a badly ordered declaration.

Changelog

  • PHP 3.0Rest elements in tuple types, plus tuples usable as the type of a rest parameter, which is what makes typed argument forwarding possible.
  • PHP 3.4`readonly` tuple types, and `as const` on an array literal now infers a readonly tuple rather than a widened array.
  • PHP 4.0Labelled tuple elements and variadic tuple types (generic spreads such as `[...T, boolean]`).
  • PHP 4.2Rest elements permitted in leading and middle positions, e.g. `[...boolean[], string, number]`.

A tuple type is an array type that knows exactly how many elements it has and exactly which type sits at each position. [string, number] accepts ["hello", 42] and rejects [42, "hello"], and destructuring it gives correctly typed bindings without any casts.

The three modifiers

An optional element ([number, number, number?]) may only follow the required ones, and it turns length into a union such as 2 | 3 — which is what lets you branch on the length and have the compiler follow you. A rest element ([id: number, ...cells: string[]]) absorbs any number of trailing elements; since 4.2 it may also sit at the front or middle. Labels are pure documentation: they show up in hints and error messages, they do not constrain the names you destructure into, and it is all-or-nothing — label one element and you must label them all.

When to reach for it

Reach for a tuple when position carries meaning and the caller will destructure immediately: a [value, setter] pair, a [key, value] entry, a fixed coordinate, a parsed record row. If callers will pass the value around and read fields by name later, an object type is kinder — nobody remembers what index 3 was.

Example

Try it yourself
Loading editor…
Press Run to execute the code.

When to use it

  • Return a `[value, setter]` pair from a state hook so callers can name both halves themselves.
  • Type the elements of `Object.entries` output as `[key, value]` rather than `(string | T)[]`.
  • Describe a parsed CSV row whose first columns are fixed and the rest are repeating.
  • Type a rest parameter list so arguments can be captured, forwarded and spread with full checking.

Frequently asked questions

What is the difference between a tuple and an array type?

An array type says every element has the same type and says nothing about length; a tuple type fixes the length and gives each position its own type. `[string, number]` and `(string | number)[]` accept different values: the tuple rejects a wrong order or a third element, the array accepts both.

Why is my `as const` array rejected where a tuple is expected?

Because `as const` produces a `readonly` tuple, and readonly array-likes are not assignable to mutable ones. Change the parameter to `readonly [number, number]`, or copy the value with a spread before passing it.

Do tuple labels change how I destructure?

No, labels are documentation only. They appear in editor hints, quick info and error messages, but the variable names you destructure into are entirely your own. The only rule they impose is that if any element is labelled, every element must be.

More examples

A state pair and a row with a rest element

Prints "initial: 0", "set -> 5", then "1 ada math" and "2 grace navy|compiler". The labels `value`/`setValue` are what an editor shows on hover, but the destructuring names them `count` and `setCount` freely. In `CsvRow` the first two positions are typed individually and everything after is absorbed by the rest element, so both rows fit despite differing lengths.

Example · typescript
type UseState<T> = [value: T, setValue: (next: T) => void];

function makeState<T>(initial: T): UseState<T> {
  let current = initial;
  return [current, (next: T) => { current = next; console.log("set ->", next); }];
}

const [count, setCount] = makeState(0);
console.log("initial:", count);
setCount(5);

type CsvRow = [id: number, name: string, ...tags: string[]];
const rows: CsvRow[] = [[1, "ada", "math"], [2, "grace", "navy", "compiler"]];
for (const [id, name, ...tags] of rows) console.log(id, name, tags.join("|"));

Tuples as rest parameters, for argument forwarding

Prints "INFO: started" then "[app] WARN: slow query (320, 410)". Because a tuple type can be the type of a rest parameter, the wrapper forwards its arguments with `...args` and every position is still checked — pass "debug" as the level and the call fails at the wrapper, not deep inside `log`.

Example · typescript
type Args = [level: "info" | "warn", message: string, ...details: number[]];

function log(...args: Args): string {
  const [level, message, ...details] = args;
  return `${level.toUpperCase()}: ${message}${details.length ? ` (${details.join(", ")})` : ""}`;
}

function withPrefix(prefix: string, ...args: Args): string {
  return prefix + log(...args);   // spread checks against the tuple
}

console.log(log("info", "started"));
console.log(withPrefix("[app] ", "warn", "slow query", 320, 410));

Discussion

  • Be the first to comment on this lesson.