Tuple Types
Fixed-length arrays with per-position types — useState pairs, CSV rows, rest parameters, and labeled tuples in TypeScript.
- typescript
- tuple-types
A tuple is an array type with a known length and a type per index:
type Pair = [string, number];
const entry: Pair = ['retries', 3];
Unlike string[], position 0 is not interchangeable with position 1.
Docs: Tuple Types, Variadic tuple types.
Why tuples exist
// Weak
function useThing(): (string | number)[] {
return ['count', 0];
}
// Strong
function useThing(): [string, number] {
return ['count', 0];
}
const [label, value] = useThing();
// label: string, value: number
React’s useState return type is essentially a tuple: [state, setter].
Labeled tuples (readability)
type Range = [start: number, end: number];
function slice(range: Range) {
const [start, end] = range;
return { start, end };
}
Labels are documentation for the type system — they don’t create runtime fields.
Optional and rest elements
type CssRule = [property: string, value: string, important?: boolean];
type StringBooleans = [string, ...boolean[]];
Variadic tuples power typed compose and middleware helpers:
type Head<T extends unknown[]> = T extends [infer H, ...unknown[]] ? H : never;
type Tail<T extends unknown[]> = T extends [unknown, ...infer R] ? R : never;
as const produces readonly tuples
const point = [10, 20] as const;
// readonly [10, 20]
function move(p: readonly [number, number]) {
return [p[0] + 1, p[1] + 1] as const;
}
Open-ended lists vs fixed pairs
| Use | Type |
|---|---|
| List of tags | string[] |
| Key/value pair | [string, string] |
| RGB | [number, number, number] |
| React state slot | [T, Dispatch<…>] |
If length varies freely, use an array. If positions mean different things, use a tuple.
Destructuring parameters
function distance([x1, y1]: [number, number], [x2, y2]: [number, number]) {
return Math.hypot(x2 - x1, y2 - y1);
}
Readonly tuples
type ImmutablePair = readonly [string, number];
Prevents push and index assignment through the type.
Footguns
- Assignability to arrays — tuples are special arrays; excess length rules can surprise.
[string]vsstring[]— one-element tuple is not a list.- Inference without
as const—[1, 2]may infernumber[]. - Empty tuple
[]— means empty; useful for “no args”.
const t: [number, number] = [1, 2];
const arr: number[] = t; // ok
// const t2: [number, number] = arr; // error — length unknown
Interview out-loud answer
“Tuples fix length and per-index types — good for pairs and coordinates. Arrays are for homogeneous open lists. I use labeled tuples for clarity and as const when I need literal readonly tuples.”
Variadic example
function concat<T extends unknown[], U extends unknown[]>(a: [...T], b: [...U]): [...T, ...U] {
return [...a, ...b];
}
const r = concat([1, 2] as const, ['a'] as const); // [1, 2, 'a']
Variadic tuples power typed middleware pipelines and Promise.all style helpers in libraries.
Extra practice
Write a minimal demo in a scratch file or the playground: one happy path, one failure path, and one boundary input. If you cannot exhibit a bug that the pattern prevents, you do not own the concept yet — re-read the primary docs linked below and tighten the example until the failure is obvious.
Related on this site
- Basic types and annotations
- Readonly and const assertions
- Function overloads
- Generics basics
- Literal types
Further reading
Related guides
- Basic Types and AnnotationsPrimitives, arrays, objects, and function annotations TypeScript actually checks — plus where inference is enough and where it is not.
- Branded Types for IDsNominal-style UserId vs OrderId in TypeScript — prevent ID mixups at compile time with brands, parsers, and form boundaries.
- Conditional Types IntroT extends U ? X : Y — how TypeScript picks types from conditions, distributes over unions, and powers utility types you already use.
- Declaration Files and DefinitelyTypedHow .d.ts files describe JS to TypeScript, when to use @types packages, module augmentation, and writing minimal ambient types for untyped libs.
- Discriminated Unions for UI StateModel loading, success, and error as mutually exclusive variants so TypeScript and your UI cannot show impossible states.