Arrays, Tuples & Readonly Arrays

beginner arrays tuples readonly immutability

Lists in TypeScript come in three flavors. Most code uses regular arrays, but tuples and readonly arrays solve real problems too.

Arrays — variable length, one type

Two equivalent syntaxes. Use whichever you prefer (T[] is more common).

const nums: number[] = [1, 2, 3];
const names: Array<string> = ["a", "b"];

nums.push(4);   // ✅
nums.push("5"); // ❌ string not assignable to number

Tuples — fixed length, fixed types per position

A tuple is an array where each index has its own type and the length is known. Think of it like an unnamed record.

type RGB = [number, number, number];
const red: RGB = [255, 0, 0];

const pair: [string, number] = ["age", 27];
// pair[0] is string, pair[1] is number

The classic use case: returning multiple values from a function (like React’s useState).

function useCounter(initial: number): [number, () => void] {
  let count = initial;
  const inc = () => { count++; };
  return [count, inc];
}

const [count, increment] = useCounter(0);

Labeled tuples — better readability

We can name each slot for documentation. Labels don’t affect the type, just IDE hints.

type Range = [start: number, end: number];
const r: Range = [0, 10]; // hovering shows 'start' and 'end'

Optional and rest in tuples

type Optional = [string, number?];           // second item is optional
type Rest = [string, ...number[]];           // string followed by any number of numbers

const a: Optional = ["x"];      // ✅
const b: Rest = ["tag", 1, 2, 3]; // ✅

readonly arrays — can’t mutate

Adding readonly (or using ReadonlyArray<T>) blocks push, pop, splice, index assignment — anything that mutates.

const fixed: readonly number[] = [1, 2, 3];
fixed.push(4);   // ❌ Property 'push' does not exist on readonly number[]
fixed[0] = 99;   // ❌ Index signature is readonly

Why bother? Function parameters mostly. We signal that we won’t mutate the caller’s array.

function sum(nums: readonly number[]): number {
  // can't accidentally modify nums here
  return nums.reduce((a, b) => a + b, 0);
}

readonly tuples

const point: readonly [number, number] = [1, 2];
point[0] = 5; // ❌

as const — make everything readonly literally

Slapping as const at the end gives us a deeply readonly tuple of literals. Great for constants.

const directions = ["up", "down", "left", "right"] as const;
// type: readonly ["up", "down", "left", "right"]

type Direction = typeof directions[number]; // "up" | "down" | "left" | "right"

When to use which

  • Array T[] — most code. Lists of homogeneous things.
  • Tuple [A, B] — multiple return values, fixed pairs (coordinates, key-value).
  • readonly — parameters we shouldn’t mutate, public constants.
  • as const — derive a union of string literals from an array.

Interview soundbite

“Arrays are homogeneous and variable-length. Tuples are fixed-length with per-position types — great for returning multiple values. readonly prevents mutation, useful for function parameters and shared constants.”