skip to content

In TypeScript, what do the labels in the tuple type `[first: string, second?: number]` change, and what rule does the compiler enforce about labelling?

level: middleimportance: nice to knowfreq 24%

answer

  1. names for the reader, not the checker
  2. assignability is unchanged
  3. think parameter hints at call sites
  4. label every element or none
  5. erased like the rest of the type layer

basics

~20 s

Labels are documentation for humans and tooling: they show in editor hints and become the parameter names when the tuple is used as a rest parameter list. They do not affect type identity or assignability. The rule is all-or-nothing — every element must be labelled, or none.

solid answer

~40 s

Labelled tuple members name each position. The names show up in editor hints and error messages, and — the reason the feature exists — when the tuple is used as a rest parameter type, its labels become the displayed parameter names of that function. What they do **not** do is change the type: `[first: string, second?: number]` and `[string, number?]` are the same type, mutually assignable, and a differently-labelled tuple is assignable too. Labels are erased with everything else, so there is no runtime trace. The one hard rule is that labelling is all-or-nothing per tuple — mixing labelled and unlabelled elements is a compile error. Note the syntax details: the optional marker goes after the label as `second?: number`, and a rest element is written `...rest: boolean[]`.

code

typescript · 11 lines
typescript
type Handler = [event: string, times?: number, ...flags: boolean[]];

declare function on(...args: Handler): void;

on('click');
on('click', 2, true);

// Labels carry no type identity: these are the same type.
const pair: [x: number, y: number] = [1, 2];
const renamed: [width: number, height: number] = pair;
console.log(renamed);

go deeper

for a junior

Recognise the syntax and know the labels are names for readers and editors, not a new kind of constraint on the value.

for a middle

Explain that labels leave type identity and assignability untouched, that labelling is all-or-nothing per tuple, and that their real payoff is parameter names when the tuple types a rest parameter.

for a senior

Push back when someone reaches for labels to enforce meaning — that needs distinct types or an object — and label tuples in published declaration files where call-site hints are what consumers actually read.

for a principal

Set the convention: labelled tuples for exported signatures assembled from tuple types, named object properties wherever the distinction must be enforced rather than merely communicated.

## What a labelled tuple member is A tuple type normally lists bare types: `[string, number]`. TypeScript also lets you attach a name to each position: ```typescript type Coordinate = [x: number, y: number]; type Range = [start: number, end?: number]; type Args = [message: string, ...tags: string[]]; ``` The syntax deliberately mirrors a parameter list, and that is the clue to what it is for. ## What labels do — and do not — affect Labels are **purely documentary**. They participate in no type relationship: ```typescript type A = [x: number, y: number]; type B = [number, number]; type C = [width: number, height: number]; const a: A = [1, 2]; const b: B = a; // ok — same type const c: C = a; // ok — labels do not affect assignability ``` So you cannot use labels to prevent a width/height pair being passed where an x/y pair is expected. If you need the compiler to reject that, you need distinct element *types* — a branded or literal-tagged model, not a rename. What labels do affect is what a human sees. Hovering a value of type `Coordinate` shows `[x: number, y: number]`; error messages quote the labelled form; and completion for a call whose parameters come from a labelled tuple offers the real names. ## The reason the feature exists: rest parameters Before labels, typing a rest parameter with a tuple produced a signature whose parameters had no meaningful names — tooling showed placeholders like `args_0`, `args_1`. Labels fix exactly that: ```typescript declare function on(...args: [event: string, times?: number, ...flags: boolean[]]): void; on('click'); on('click', 2); on('click', 2, true, false); ``` The call site now sees `event`, `times` and `flags` in its hints, indistinguishable from a hand-written parameter list. That matters most in declaration files and in generic plumbing where a signature's parameters are assembled from a tuple type rather than written out. ## The all-or-nothing rule Within one tuple type, either every element carries a label or none does. Mixing them is a compile error, whose message is that tuple members must all have names or all not have names: ```typescript // type Bad = [first: string, number]; // Error: label some, label all type Good1 = [first: string, second: number]; type Good2 = [string, number]; ``` The rule exists because a half-labelled tuple reads ambiguously — `[first: string, number]` looks like it might be describing something other than a two-element list. ## Syntax details worth getting right The optional marker attaches to the **label**, not the type: `second?: number`, never `second: number?`. A rest element keeps its spread and takes an array type: `...tags: string[]`. And a label is not a binding — it does not create a variable, and destructuring is free to use entirely different names: ```typescript const r: [start: number, end: number] = [1, 9]; const [from, to] = r; // labels impose nothing on these names ``` ## Erasure Like the rest of the type layer, labels vanish at compile time. There is no metadata to reflect over and no way to read the label of a tuple position at runtime; if the names must exist in the emitted program, the value has to be an object with real properties, not a labelled tuple. ## Practical guidance Label a tuple whenever it is used as a rest-parameter type or is part of a published API surface — the cost is nothing and the readability gain at call sites is real. For a local two-element pair whose meaning is obvious from context, labels are optional noise. And when you catch yourself wanting labels to *enforce* something rather than to document it, that is the signal to switch to an object type, where property names are genuinely part of the type.

  • Can labelling stop a width/height pair being passed where an x/y pair is expected?
    No. Labels are documentation and play no part in assignability, so `[width: number, height: number]` and `[x: number, y: number]` are interchangeable. To make the compiler object you need the element types themselves to differ — branded number types, or an object type with real property names, which are part of the type.
  • Where does the optional marker go on a labelled element?
    After the label: `second?: number`. Writing `second: number?` is a syntax error. A rest element keeps the spread before the label, as `...tags: string[]`. The mental model that keeps this straight is that a labelled tuple element is written exactly like a function parameter.

saying these in an interview costs you the question

  • Claims labels make two same-shaped tuples incompatible
  • Thinks labels are readable at runtime
  • Writes `second: number?` instead of `second?: number`
  • Believes labels must match the destructured variable names
  • Mixes labelled and unlabelled elements in one tuple

context