C.W.K.
Stream
Lesson 02 of 05 · published

Tuples: Position Matters

~10 min · arrays-tuples, tuples, positional-types

Level 0Curious
0 XP0/93 lessons0/23 achievements
0/100 XP to next level100 XP to go0% complete
"A tuple is an array where each slot has its own job."

What a tuple is

A tuple is a fixed-length array where each position can have its own type. [string, number] is an array of exactly two elements: the first is a string, the second is a number. The compiler tracks both the length and the per-position type.

Tuples are useful when the position carries meaning. Coordinate pairs ([x, y]), key-value pairs ([key, value]), the return-with-status pattern ([error, result]) — all of these are conceptually positional, and a tuple type lets the compiler enforce that.

Optional and rest elements in tuples

Tuples support modifiers similar to function parameters:

  • Optional element: [string, number?] — second element may be missing.
  • Rest element: [string, ...number[]] — first element is a string, then any number of numbers.
  • Named tuple elements: [name: string, age: number] — purely cosmetic labels that show up in IDE hover. They don't change behavior but help readability.

When tuples beat objects, and when they don't

Tuples shine when:

  • The two-or-more values are commonly returned together and the positions have well-known meaning (React's useState returns [value, setter]).
  • You want destructuring to match the shape directly: const [x, y] = getCoords().
  • The number of values is small (2–3) and the names would be obvious context.

Objects beat tuples when:

  • There are 4+ values — humans lose track of position.
  • The values are heterogeneous in role and a name would clarify ({ user, role, permissions } vs [User, Role, Permission[]]).
  • The shape may grow over time — adding to a tuple breaks every destructuring call site.
The rule of thumb: Use tuples when the positional shape is the contract (return-with-status, useState). Use objects when names are the contract. The choice signals intent to the next reader.

Inference: arrays by default, tuples on request

Array literals widen to arrays by default. To get tuple types from a literal, use either explicit annotation or as const:

const a = [1, 'two'];           // (string | number)[] — array of union
const b: [number, string] = [1, 'two'];   // tuple, explicit
const c = [1, 'two'] as const;            // readonly [1, 'two'] — tuple + literal

Pippa's confession

cwkPippa's frontend uses tuples sparingly — mostly for React's useState/useReducer (which the React types already declare), and for a few utility functions that return [ok, value] or [error, result]. Most data is object-shaped. Tuples are like literal types — narrow, sharp tools that win when the shape really is positional.

Code

Tuples with all the modifiers·typescript
// Tuples — fixed length, per-position types.

type Coord = [number, number];
const origin: Coord = [0, 0];
const point: Coord = [3, 4];
// const bad: Coord = [3];          // ❌ Missing element
// const bad2: Coord = [3, 4, 5];   // ❌ Too many elements

// Optional and rest elements.
type OptionalSecond = [string, number?];
const x1: OptionalSecond = ['hi'];
const x2: OptionalSecond = ['hi', 42];

type Spread = [string, ...number[]];
const y: Spread = ['header', 1, 2, 3, 4];

// Named tuple elements — labels for readability.
function getRange(): [start: number, end: number] {
  return [0, 100];
}
const [s, e] = getRange();  // s and e named for clarity
Tuples as contracts — useState and Go-style error returns·typescript
// The useState pattern — positional contract.

type StateHook<T> = [T, (next: T) => void];

function useCounter(initial: number): StateHook<number> {
  let value = initial;
  const set = (next: number) => { value = next };
  return [value, set];
}

const [count, setCount] = useCounter(0);
setCount(count + 1);

// Return-with-status pattern.
function parse(input: string): [error: string | null, result: number | null] {
  const n = Number(input);
  if (Number.isNaN(n)) return ['not a number', null];
  return [null, n];
}

const [err, result] = parse('42');
if (err === null) console.log(result);

External links

Exercise

Write a divmod function that takes a numerator and divisor and returns the quotient and remainder as a tuple. Use named tuple elements in the return type. Then call it and destructure into clearly-named variables. Compare with an object-returning version — which one would you ship?
Hint
For two values that conceptually go together (quotient and remainder), a tuple with named elements gives you both the destructuring ergonomics and the documentation. For three or more, an object is usually clearer.

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue

Comments 0

🔔 Reply notifications (sign in)
Sign inPlease sign in to comment.

No comments yet — be the first.