Collections are everywhere, and TypeScript gives you precise tools for them: arrays for homogeneous lists of any length, tuples for fixed-length sequences with a known type at each position, and readonly variants that prevent mutation. This lesson covers all three, plus practical patterns like as const.
There are two equivalent syntaxes:
const scores: number[] = [90, 85, 77];
const names: Array<string> = ["Ada", "Grace"];Most style guides prefer number[] for simple element types. Arrays of complex types read well either way:
interface Todo { title: string; done: boolean }
const todos: Todo[] = [
{ title: "Learn tuples", done: false },
];Array methods stay fully typed: todos.filter(t => t.done) returns Todo[], and scores.map(s => s * 2) returns number[]. If you start from an empty array, annotate it — otherwise TypeScript infers never[] or a too-loose type:
const results: string[] = []; // annotate empty arraysParentheses matter:
const mixed: (string | number)[] = ["a", 1, "b"]; // each element is string or number
const oneKind: string[] | number[] = [1, 2, 3]; // the whole array is one kindA tuple declares exactly which type lives at each index:
type Point = [number, number];
const origin: Point = [0, 0];
type HttpResponse = [status: number, body: string]; // labeled elements
const res: HttpResponse = [200, "OK"];
const [status, body] = res; // destructuring keeps the typesTuples power patterns like React's useState, which returns [value, setter]. Labels (status:, body:) are purely documentation but make signatures much clearer.
Tuples can include optional and rest elements:
type Range = [start: number, end?: number];
type Row = [id: number, ...cells: string[]];Prefix with readonly (or use ReadonlyArray<T>) to forbid mutation:
const weekdays: readonly string[] = ["Mon", "Tue", "Wed"];
weekdays.push("Sat"); // Error: push does not exist on readonly string[]
weekdays[0] = "Sun"; // Error: index signature is readonly
const first = weekdays[0]; // reading is fineAccepting readonly T[] in function parameters is a great habit: it promises callers you will not mutate their data, and a normal T[] is always assignable to readonly T[] (but not the reverse).
function total(values: readonly number[]): number {
return values.reduce((sum, v) => sum + v, 0);
}The as const assertion freezes a literal's types: arrays become readonly tuples and strings become literal types:
const sizes = ["sm", "md", "lg"] as const;
// type: readonly ["sm", "md", "lg"]
type Size = (typeof sizes)[number]; // "sm" | "md" | "lg"This one-liner — derive a union from a constant array — is one of the most useful tricks in TypeScript: a single source of truth for both runtime values and compile-time types.
T[] for lists of any length; annotate empty arrays explicitly.(A | B)[] allows mixed elements; A[] | B[] means one kind of array.readonly T[] prevents mutation and is ideal for function parameters.as const turns literals into readonly tuples and literal unions.Next lesson: Enums and Literal Types — named constants and exact-value types.