A TypeScript tuple is an array with a fixed number of elements, where each position has its own type. [string, number] means exactly two elements: a string first, then a number. You write the types in square brackets in the order the values appear.
At runtime a tuple is a plain JavaScript array. Everything a tuple adds (the fixed length and the type at each position) is checked by the compiler and erased.
Tuple Syntax
| Tuple type | Accepts | length type |
|---|---|---|
[string, number] | exactly a string, then a number | 2 |
[x: number, y: number] | the same, with labels for readability | 2 |
[number, number, number?] | 2 or 3 numbers | 2 | 3 |
[string, ...number[]] | a string, then any number of numbers | number |
[...string[], number] | any number of strings, then a number | number |
readonly [number, number] | a pair that cannot be modified | 2 |
[] | only an empty array | 0 |
Each form is explained below. The type of length is worth noticing: for a fixed tuple it is a literal type, so the compiler knows pair.length is exactly 2.
What the Compiler Checks
A tuple type fixes the number of elements, their order, and the type at each position. Getting any of them wrong is a compile error:
index.ts(2,7): error TS2322: Type '[string]' is not assignable to type '[string, number]'.
Source has 1 element(s) but target requires 2.
index.ts(3,36): error TS2322: Type 'number' is not assignable to type 'string'.
index.ts(3,40): error TS2322: Type 'string' is not assignable to type 'number'.
index.ts(5,16): error TS2493: Tuple type '[string, number]' of length '2' has no element at index '2'.
A plain array could never catch the last one: for string[], arr[2] is simply a string that happens to be undefined at runtime.
Named Tuple Elements
Labels document what each position means. They change nothing about the type or how you index it, but editors show them in hovers and signature hints, which makes [number, number] much less mysterious.
Since TypeScript 5.2 you can label some positions and leave others unlabeled, as in [first: string, number]. Labels are for readers only: [x: number, y: number] and [number, number] are the same type and assignable to each other.
Optional Elements
A ? after an element type makes that position optional. Optional elements must come after the required ones, and each one widens the length type.
Reading an optional element gives T | undefined, so a default in the destructuring pattern (a = 1) or a check is needed before arithmetic.
Rest Elements
A rest element, ...T[], stands for any number of elements of type T. It can be at the end, the start, or the middle, with at most one per tuple.
The length of a tuple with a rest element is number, since the size is no longer fixed. What remains fixed is where the typed positions are.
Readonly Tuples and as const
readonly [T, U] removes push, pop, splice and index assignment, which is what a fixed-length value should be. Writing as const after an array literal infers a readonly tuple of literal types.
(typeof SIZES)[number] turns the tuple into a union of its element types, a pattern covered on indexed access types. A readonly tuple cannot be passed to a parameter typed as a mutable tuple, so functions that only read should accept readonly [number, number].
The readonly check is compile-time only. At runtime the array is not frozen (the assignment above really ran, as the output shows), so use Object.freeze if you need a runtime guarantee.
Returning a Tuple from a Function
Returning several values as a tuple is how React's useState works (const [value, setValue] = useState(0)). The catch: an array literal in a return is inferred as an array, not a tuple.
index.ts(9,13): error TS2365: Operator '+' cannot be applied to types 'number | (() => number)' and 'number'.
index.ts(10,1): error TS2349: This expression is not callable.
Not all constituents of type 'number | (() => number)' are callable.
Type 'number' has no call signatures.
The function returns (number | (() => number))[], so both destructured names get the union type. There are two fixes: annotate the return type, or add as const.
A tuple return value lets callers name the parts whatever they like. When there are more than two or three values, or the order is not obvious, return an object instead: { count, increment } documents itself.
Tuples as Function Parameters
A rest parameter typed as a tuple describes a whole argument list, including optional arguments. This is how the built-in Parameters<T> utility type represents a function's parameters.
Spreading a tuple into a call type-checks each argument by position, which a spread of (string | number)[] could not.
Tuple vs Array
Array (string | number)[] | Tuple [string, number] | |
|---|---|---|
| Length | any | fixed (or bounded by optional and rest elements) |
Type of x[0] | string | number | string |
Type of x[5] | string | number | compile error TS2493 |
Type of length | number | 2 |
| Order of types | not tracked | tracked |
| Runtime value | JavaScript array | the same JavaScript array |
| Typical use | lists of similar items | small fixed groups: pairs, coordinates, [key, value], multiple return values |
Tuples also show up in built-in types. Object.entries(obj) returns [string, T][], and a Map is constructed from [key, value] tuples:
One trap: a mutable tuple still has every array method, so pair.push(3) compiles on a [string, number] and quietly makes a three-element array whose type says two. Declaring tuples readonly closes that hole. And since types are erased, data from outside the program (JSON, an API) is not checked against a tuple type at runtime: validate its length and element types before trusting it.
Variadic Tuple Types
Tuple types can spread other tuple types, [...T, ...U]. Combined with generics, this types functions that concatenate or prepend while keeping every position:
Library types lean on tuple inference too: Promise.all([fetchUser(), fetchPosts()]) resolves to a tuple with one type per input promise.
Frequently Asked Questions
What is a tuple in TypeScript?
A tuple is an array type with a fixed length where each position has its own type: [string, number] is exactly two elements, a string then a number. At runtime it is an ordinary JavaScript array; the length and the per-position types are checked only at compile time.
What is the difference between a tuple and an array in TypeScript?
An array type like (string | number)[] has any length and every element has the same (union) type, so arr[0] is string | number. A tuple like [string, number] has a known length and t[0] is string, t[1] is number, and t[2] is a compile error.
How do I return a tuple from a function in TypeScript?
Annotate the return type, function f(): [number, string], or end the return expression with as const, which gives a readonly tuple. Without either, return [count, setCount] is inferred as an array of a union, like (number | (() => void))[], and destructuring gives union types.
What are named tuple elements?
Labels on the positions, [name: string, age: number]. They do not change the type or how you access it (still t[0]), but editors show them in hovers and in the parameter hints of functions whose parameters are typed as a tuple. Optional and rest elements work with labels: [x: number, y?: number], [head: string, ...rest: number[]].
Can you push to a tuple in TypeScript?
On a mutable tuple, yes: push compiles, because tuples inherit the array methods, even though it breaks the fixed length. Declare the tuple readonly (or create it with as const) and push, pop and index assignment become compile errors.