Arrays and Tuples

This section documents the current TypeScript release line as published at the official TypeScript documentation, which is the reference these pages are written and verified against. No specific patch version is pinned.

This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production, since TypeScript iterates quickly.

This section’s bibliography lists the reference material consulted while preparing these pages.

An array type describes a list whose elements share one type and whose length is free to change; a tuple type fixes the length and gives each position its own type. Both live on the Object Types handbook page. This page builds on Everyday Types and hands off to Generics where element types become type parameters.

T[] versus Array<T>

The two spellings compile to the same type — Array<T> is the interface, T[] is sugar for it. Pick one and stay consistent; most codebases use T[] and reserve the generic form for nested or computed element types where it reads better.

const a: number[] = [1, 2, 3];
const b: Array<number> = [1, 2, 3];               // identical type to `a`
const grid: number[][] = [[1], [2, 3]];           // clearer than Array<Array<number>>
const pairs: Array<[string, number]> = [["a", 1]]; // generic form reads better here

function wrap<T>(x: T): T[] {
  return [x];
}

Readonly arrays

readonly T[] and ReadonlyArray<T> are the same immutable view: no push, pop, splice or index assignment, and no implicit widening back to a mutable T[]. Use it for parameters you never mutate.

function total(xs: readonly number[]): number {
  // xs.push(0);   // Error: Property 'push' does not exist on type 'readonly number[]'
  return xs.reduce((t, x) => t + x, 0);
}

const frozen: ReadonlyArray<string> = ["a", "b"];
const escaped: string[] = frozen;   // Error: 'readonly string[]' is not assignable to 'string[]'

Index access is unsound by default

arr[i] is typed as T, never T | undefined, even when i is out of range — TypeScript trades soundness for ergonomics here. Enabling noUncheckedIndexedAccess adds | undefined to every index and dynamic property access, forcing a check.

const names = ["Ada", "Alan"];
const third = names[2];        // typed string, but actually undefined at runtime

// With noUncheckedIndexedAccess enabled:
const first = names[0];        // typed string | undefined
first.toUpperCase();           // Error: 'first' is possibly 'undefined'
if (first !== undefined) {
  first.toUpperCase();         // narrowed to string
}

Evolving any from an empty array literal

An un-annotated let acc = [] starts as an evolving any[]: each push or index write widens the element type from what you add, and the type freezes at the first read. It only applies to an un-annotated let with no initializer type — annotate the variable to opt out.

let acc = [];        // evolving any[]
acc.push(1);         // now number[]
acc.push("x");       // now (string | number)[]
const out = acc;     // read -> frozen as (string | number)[]

let typed: number[] = [];   // no evolution -- number[] from the start

Tuples: fixed-length typed positions

A tuple type is a bracketed list of element types. Length and per-position types are enforced, and array destructuring keeps the position types.

type Point = [number, number];
const p: Point = [3, 4];
const [x, y] = p;                 // x, y both number
// const bad: Point = [1];        // Error: Type '[number]' is not assignable to type 'Point'

function divmod(a: number, b: number): [number, number] {
  return [Math.floor(a / b), a % b];
}

Optional and rest elements

A trailing ? marks an optional slot; a …​T[] element absorbs any number of values. Since TS 4.2 the rest element no longer has to come last. Both turn the tuple’s length into a range.

type Vec = [number, number, number?];          // length 2 or 3
const v2: Vec = [1, 2];
const v3: Vec = [1, 2, 3];

type Path = [string, ...number[]];             // one string, then any count of numbers
const road: Path = ["origin", 1, 2, 3];

type Framed = [string, ...boolean[], number];  // rest in the middle (TS 4.2+)
const f: Framed = ["a", true, false, 9];

readonly tuples

Prefix the tuple with readonly to forbid element writes and length-changing methods. Array and tuple literals passed where a readonly tuple is expected need no assertion.

type RGB = readonly [number, number, number];
const black: RGB = [0, 0, 0];
// black[0] = 255;   // Error: Cannot assign to '0' because it is a read-only property

function magnitude([a, b]: readonly [number, number]): number {
  return Math.hypot(a, b);
}
magnitude([3, 4]);

Labelled tuple elements

Each position can carry a name. Labels are documentation only — they do not affect assignability — but they surface in editor hints and in rest-parameter signatures. If one element is labelled, every element must be.

type HttpResult = [status: number, body: string];
type Range = [start: number, end: number, step?: number];

function move(...delta: [dx: number, dy: number]): void {
  const [dx, dy] = delta;
}

as const tuples

A plain array literal is inferred as an array (number[]), not a tuple. as const makes it a readonly tuple of literal types — the usual way to get a precise tuple without spelling the type out.

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

const entry = ["id", 42] as const;    // readonly ["id", 42]
type Entry = typeof entry;            // readonly ["id", 42]

Variadic tuple types

Variadic tuple types (TS 4.0) let a rest element be a generic tuple, so tuples can be concatenated, prepended to, or unpacked at the type level.

type Concat<A extends readonly unknown[], B extends readonly unknown[]> = [...A, ...B];
type AB = Concat<[1, 2], [3, 4]>;      // [1, 2, 3, 4]

function concat<A extends unknown[], B extends unknown[]>(a: [...A], b: [...B]): [...A, ...B] {
  return [...a, ...b];
}
const head: [number, number] = [1, 2];
const r = concat(head, ["a"]);         // [number, number, ...string[]]

Variadic functions with rest parameters

Typing a rest parameter with a tuple — often a generic one — models functions whose argument list has structure. Combined with variadic tuples this precisely types bind, currying, and Promise.all-style helpers.

type Args = [name: string, ...flags: boolean[]];
function log(...args: Args): void {
  const [name, ...flags] = args;
}
log("start");
log("start", true, false);

function partial<A extends unknown[], B extends unknown[], R>(
  f: (...args: [...A, ...B]) => R,
  ...bound: A
): (...rest: B) => R {
  return (...rest) => f(...bound, ...rest);
}
const add3 = (a: number, b: number, c: number) => a + b + c;
const add5 = partial(add3, 5);   // (rest: [number, number]) => number
add5(2, 3);                      // 10

Inference caveats and as const

Array literals infer to arrays, and rest-tuple type parameters infer loosely — widened to an array — unless a const type parameter or a call-site as const pins them. Reach for as const at the call, or declare the parameter <const T> (see Generics), when you need the literal tuple back.

function pack<T extends unknown[]>(...items: T): T {
  return items;
}
const loose = pack(1, "a");          // [number, string]
const nested = pack([1, 2], "a");    // [number[], string] -- inner literal widened

function packConst<const T extends unknown[]>(...items: T): T {
  return items;
}
const tight = packConst(1, "a", [2, 3]);   // [1, "a", readonly [2, 3]]

Spreads and rests in calls and literals

Spreading a tuple into a call maps positionally onto parameters; spreading into an array literal builds a new tuple or array type. A rest in a binding pattern collects the leftovers — as a tuple when the source is a tuple, otherwise an array.

function line(x1: number, y1: number, x2: number, y2: number): void {}
const from: [number, number] = [0, 0];
const to: [number, number] = [10, 5];
line(...from, ...to);            // OK: four args, types line up

const more = [...from, ...to, 1];             // number[]
const tagged = [...from, "label"] as const;   // readonly [0, 0, "label"]

const [first, ...rest] = [1, 2, 3] as const;  // first: 1, rest: [2, 3]