Articles

TypeScript Tuple Types Explained

A TypeScript tuple is a fixed-length array where each position has its own type. How tuples work, labeled elements, and when to use them over arrays.

Takina Takina · · 4 min read
Abstract programming language symbols

A tuple in TypeScript is an array type with a fixed length where each position has its own, independently specified type — unlike a regular array type, where every element must share the same type. Tuples let you model “a pair of exactly these two things” or “these three things in this order” with full type safety at each index.

Basic syntax

let point: [number, number] = [3, 7];
let entry: [string, number] = ["age", 30];

point[0]; // number
entry[1]; // number
entry[0]; // string

Compare this to a regular array type, (string | number)[], which says “any number of elements, each either a string or a number” — it doesn’t guarantee length, order, or which type is at which index. A tuple pins all three down. entry[1].toUpperCase() is a compile error, because TypeScript knows position 1 is a number, not a string; a plain union-typed array would let that call through and fail only at runtime.

Labeled tuple elements

Since TypeScript 4.0, tuple elements can carry names for documentation and better editor tooltips, without changing runtime behavior:

function makeRange(start: number, end: number): [start: number, end: number] {
  return [start, end];
}

The labels don’t create real object keys — the value is still a plain array ([0, 10]) — but they show up in IDE hints and make function signatures using tuples far more readable than bare [number, number].

Optional and rest elements

Tuples support optional elements with ? and a rest element with ..., which lets you model variable-length patterns while still constraining the fixed part:

type Padding = [top: number, right?: number, bottom?: number, left?: number];

type CommandArgs = [command: string, ...flags: string[]];

const args: CommandArgs = ["deploy", "--force", "--verbose"];

CommandArgs requires a string in the first position and allows any number of additional string elements after it — this is exactly the shape of JavaScript’s built-in rest parameters, applied to a type instead of a function call.

Where tuples show up in practice

The most common place developers encounter tuples without necessarily naming them is React’s useState:

function useToggle(initial: boolean): [boolean, () => void] {
  const [state, setState] = useState(initial);
  const toggle = () => setState((s) => !s);
  return [state, toggle];
}

useState itself returns a tuple — [value, setter] — which is precisely why you can destructure it positionally (const [count, setCount] = useState(0)) rather than by property name. If useState returned a plain object type instead, you’d be stuck with fixed property names like .value and .setter for every call site.

Tuples are also the natural type for fixed-format data: RGB color values ([number, number, number]), a 2D coordinate, a key-value pair before it’s inserted into a Map, or a function’s argument list when working with utility types like Parameters<T>, which itself returns a tuple of a function’s parameter types.

readonly tuples

Tuples can be made immutable at the type level with readonly, which blocks mutating methods like push, pop, and index assignment:

function distance(a: readonly [number, number], b: readonly [number, number]): number {
  const dx = a[0] - b[0];
  const dy = a[1] - b[1];
  return Math.sqrt(dx * dx + dy * dy);
}

const origin: readonly [number, number] = [0, 0];
origin[0] = 5; // compile error: index signature is readonly

This is useful for function parameters where you want to guarantee the caller’s tuple isn’t accidentally mutated inside the function, and it pairs well with as const, which infers a literal readonly tuple type instead of widening to a plain array:

const rgb = [255, 128, 0] as const; // readonly [255, 128, 0]

Without as const, TypeScript would infer rgb as number[] — a mutable array of unspecified length — losing both the fixed length and the specific literal values.

Tuples vs interfaces for structured data

For anything beyond two or three positional values, an object or interface is almost always more readable than a tuple — { r: number; g: number; b: number } self-documents at every call site, while [number, number, number] requires the reader to remember or look up which index means what. Tuples earn their keep specifically when:

  • The order is already conventional and well known (coordinates, RGB, [error, result] pairs).
  • You’re modeling a function’s return signature that needs positional destructuring, like useState.
  • You’re working with generics that need to preserve exact positional types, such as a custom hook or a variadic function wrapper.

The takeaway

A tuple is TypeScript’s way of giving a fixed-length array per-position types instead of one shared element type. Reach for one when order and position are meaningful and conventional — coordinates, RGB values, useState-style pairs — and prefer a named object type once you have more than a couple of fields or the meaning of each position isn’t obvious from context alone.

Takina Takina · · 4 min read

Structural Typing vs Nominal Typing in TypeScript

Structural typing checks shape, not name — TypeScript treats two differently-named types as compatible if their members match, unlike nominal systems.

#TypeScript #JavaScript #Web Development
Takina Takina · · 5 min read

TypeScript Variance Explained

Variance decides when TypeScript accepts Array<Dog> where Array<Animal> is expected — covariant, contravariant, or invariant, explained with examples.

#TypeScript #JavaScript #Web Development
Takina Takina · · 5 min read

Zod vs Yup: TypeScript Schema Validation Compared

Zod infers static TypeScript types directly from its schemas; Yup was built for JavaScript form validation first. How the two approaches differ.

#TypeScript #JavaScript #Web Development