TypeScriptTuples

Tuples

A tuple is a fixed-length array where the type of each element at each position is known. While a regular array like number[] only says "every element is a number," a tuple like [string, number, boolean] says "exactly three elements: first a string, then a number, then a boolean." Tuples bring positional type safety to JavaScript's otherwise untyped arrays.

Defining a Tuple

Declare a tuple by listing the types of each position inside square brackets:

TS
// A tuple: exactly [string, number]
let person: [string, number] = ['Alice', 30];

// Access elements — types are positional
const name: string = person[0];   // string
const age: number  = person[1];   // number

// Wrong order — caught at compile time
let wrong: [string, number] = [30, 'Alice'];
// Error: Type 'number' is not assignable to type 'string'
//        Type 'string' is not assignable to type 'number'

// Wrong length — also caught
let tooShort: [string, number] = ['Alice'];
// Error: Type '[string]' is not assignable to type '[string, number]'
Tuples vs Arrays

Feature

Array (T[])

Tuple ([T, U, V])

Length

Variable

Fixed

Element types

All elements the same type

Each position has its own type

Out-of-bounds access

Returns undefined at runtime

Compile error (with noUncheckedIndexedAccess)

Use case

Lists of homogeneous items

Records with fixed structure

Common example

number[] — array of scores

[string, number] — name + age pair

Destructuring Tuples

Destructuring is the most natural way to work with tuples. TypeScript infers each variable's type from its position:

TS
const color: [number, number, number] = [255, 128, 0];
const [red, green, blue] = color;
// red: number, green: number, blue: number

// Custom variable names make the meaning clear
const [r, g, b] = color;
console.log(`rgb(${r}, ${g}, ${b})`);

// Skip elements with _
const [, secondElement] = [10, 20, 30];
// secondElement: number — 20

// Rest in destructuring
const [head, ...tail]: [number, ...number[]] = [1, 2, 3, 4];
// head: number — 1
// tail: number[] — [2, 3, 4]
Named Tuple Elements (TypeScript 4.0+)

Since TypeScript 4.0, you can label each position in a tuple. Labels improve readability and appear in IDE tooltips but do not change the type behaviour.

TS
// Named tuple elements
type Range = [start: number, end: number];
type RGB = [red: number, green: number, blue: number];
type HttpResult = [statusCode: number, body: string, headers: Record<string, string>];

const range: Range = [1, 100];
const pixel: RGB = [255, 0, 128];

// Hover in VS Code shows the label names in the tooltip
// Makes code self-documenting without needing variable renaming tricks

// Destructuring names are still arbitrary — labels are just hints
const [from, to] = range;   // from and to are valid names
const [start, end] = range; // so are start and end
Tip
Named tuple elements show up in VS Code's parameter hints and hover tooltips, making function signatures with tuple parameters much easier to understand. Use them whenever a tuple position is not self-evident.
Optional Tuple Elements

Add a ? to mark the last one or more elements of a tuple as optional:

TS
// Last element is optional
type Point2D = [x: number, y: number];
type Point3D = [x: number, y: number, z?: number];

const p2: Point3D = [1, 2];       // OK — z is optional
const p3: Point3D = [1, 2, 3];   // OK

// TypeScript enforces: optional elements must come after required ones
type Invalid = [x?: number, y: number];
// Error: A required element cannot follow an optional element
Rest Elements in Tuples

Tuples can have a rest element that captures a variable number of elements of a specific type:

TS
// Leading required elements, rest at the end
type StringWithNumbers = [string, ...number[]];

const s1: StringWithNumbers = ['hello'];             // OK — no numbers required
const s2: StringWithNumbers = ['hello', 1, 2, 3];   // OK
const s3: StringWithNumbers = [1, 'hello'];          // Error: first must be string

// Rest at the start (TypeScript 4.2+)
type NumbersWithString = [...number[], string];

const n1: NumbersWithString = ['end'];               // OK
const n2: NumbersWithString = [1, 2, 3, 'end'];     // OK

// Rest in the middle (TypeScript 4.2+)
type Sandwich = [string, ...string[], string];
// First slice, any fillings, last slice
Readonly Tuples

Just like arrays, tuples can be made readonly to prevent mutation:

TS
// Readonly tuple — prevents mutation
const point: readonly [number, number] = [10, 20];

point[0] = 99;   // Error: Cannot assign to '0' because it is a read-only property

// as const creates a readonly tuple with literal types
const direction = ['north', 'east'] as const;
// type: readonly ['north', 'east']

direction[0] = 'south';  // Error
direction.push('west');  // Error: push does not exist on readonly tuple

// Useful for function return values you don't want callers to mutate
function getCoords(): readonly [number, number] {
  return [48.8566, 2.3522]; // Paris
}
Tuples as Function Return Values

One of the most practical uses of tuples is as return values from functions that need to return multiple values. The classic example is React's useState hook:

TS
// Simulated useState — returns [value, setter] tuple
function useState<T>(initial: T): [T, (newValue: T) => void] {
  let state = initial;
  const setState = (newValue: T) => { state = newValue; };
  return [state, setState];
}

const [count, setCount] = useState(0);
// count: number
// setCount: (newValue: number) => void

setCount(1);       // OK
setCount('hello'); // Error: Argument of type 'string' is not assignable to type 'number'

TS
// Custom hook pattern — common in React
function useToggle(initialValue: boolean): [boolean, () => void] {
  let value = initialValue;
  const toggle = () => { value = !value; };
  return [value, toggle];
}

const [isOpen, toggleOpen] = useToggle(false);
// isOpen: boolean
// toggleOpen: () => void
Tuples as Function Parameters

Tuples can describe the shape of function argument lists, which is especially useful with spread syntax and rest parameters:

TS
// Spread a tuple as function arguments
type Params = [string, number, boolean];
const args: Params = ['Alice', 30, true];

function createUser(name: string, age: number, admin: boolean) {
  return { name, age, admin };
}

createUser(...args);  // OK — TypeScript verifies the spread matches the signature

// Rest parameters that form a tuple
function log(level: 'info' | 'warn' | 'error', ...parts: [string, ...unknown[]]): void {
  console.log(`[${level}]`, ...parts);
}
Tuple Type Inference Gotcha

TypeScript does not infer tuple types by default — it infers regular array types. You need to force it with as const or an explicit annotation:

TS
// Inferred as: (string | number)[] — NOT a tuple
const pair = ['Alice', 30];

// Fix 1: explicit annotation
const pair1: [string, number] = ['Alice', 30];

// Fix 2: as const — creates readonly tuple with literal types
const pair2 = ['Alice', 30] as const;
// type: readonly ['Alice', 30]

// Fix 3: helper function to infer tuple
function tuple<T extends unknown[]>(...args: T): T {
  return args;
}
const pair3 = tuple('Alice', 30);
// type: [string, number]
Note
The helper function trick works because TypeScript infers rest parameter types as tuples when the function is generic. This is a common pattern in TypeScript utility libraries.
Practical Examples
Key-Value Pairs

TS
type Entry<K, V> = [key: K, value: V];
type StringEntry = Entry<string, string>;

const entries: StringEntry[] = [
  ['name', 'Alice'],
  ['city', 'Paris'],
  ['lang', 'TypeScript'],
];

// Convert to an object
const obj = Object.fromEntries(entries);
// type: { [k: string]: string }
Result Pattern (without exceptions)

TS
// Inspired by Go-style error handling
type Result<T, E = Error> = [data: T, error: null] | [data: null, error: E];

async function trySomething(): Promise<Result<string>> {
  try {
    const data = await fetchData();
    return [data, null];
  } catch (e) {
    return [null, e instanceof Error ? e : new Error(String(e))];
  }
}

// Usage
const [data, error] = await trySomething();
if (error) {
  console.error(error.message);
} else {
  console.log(data.toUpperCase());  // TypeScript knows data is string here
}
Coordinate Systems

TS
type Coordinate2D = [x: number, y: number];
type Coordinate3D = [x: number, y: number, z: number];
type GeoCoord = [latitude: number, longitude: number];

function distance([x1, y1]: Coordinate2D, [x2, y2]: Coordinate2D): number {
  return Math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2);
}

const origin: Coordinate2D = [0, 0];
const point: Coordinate2D = [3, 4];

console.log(distance(origin, point));  // 5
Tuples vs Objects: When to Use Which

Scenario

Use Tuple

Use Object / Interface

2-3 related values, position is obvious

Yes

No

Many fields (4+)

No

Yes — names are essential

Return value from a hook

Yes — [value, setter]

Sometimes — depends on complexity

Public API function return

No — fragile

Yes — names prevent misuse

CSV row / coordinate pair

Yes

Overkill for simple pairs

Domain model with optional fields

No

Yes — optional properties are cleaner

Summary
  • Tuples are fixed-length arrays where each position has its own type

  • Use named tuple elements (TypeScript 4.0+) to make positions self-documenting

  • Optional tuple elements (?) must come after all required elements

  • Rest elements allow variable-length tuples: [string, ...number[]]

  • Add readonly to prevent mutation: readonly [number, number]

  • TypeScript infers arrays, not tuples — use as const or explicit annotation to force it

  • Tuple return types are ideal for hook-style APIs: [value, setter]

  • For 4+ related values, prefer an object or interface — names matter at that point