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]
Related pages
-
Everyday Types — primitives, objects, unions and the basic array syntax.
-
Generics — generic element types, constraints and
consttype parameters. -
tsconfig.json and Compiler Options — turning on
noUncheckedIndexedAccess,noImplicitAnyandstrict. -
Type Assertions and Modifiers —
as const,readonlyand non-null assertions in depth.