Objects, Arrays & Tuples
Object types and their modifiers
Section titled “Object types and their modifiers”An object type describes a shape by listing properties. Two modifiers carry a lot of meaning:
interface User { readonly id: string; // can't be reassigned after creation name: string; // required email?: string; // optional — string | undefined}readonlyblocks reassignment of a property. It’s compile-time only (nothing stops mutation at runtime), but it documents and enforces intent.?(optional) means the property may be absent. Crucially, an optional property has typeT | undefined, so you must handle theundefinedcase:
function greet(u: User) { u.email.toLowerCase(); // ❌ Object is possibly 'undefined'. u.email?.toLowerCase(); // ✅ optional chaining handles the absence}interface and type are nearly interchangeable for object shapes; interface can be re-opened (declaration merging) and is idiomatic for public object contracts, while type can also express unions, tuples, and mapped types. Use type when you need those; either is fine for a plain object.
Index signatures: objects as maps
Section titled “Index signatures: objects as maps”When keys aren’t known ahead of time, an index signature types them uniformly:
interface Scores { [player: string]: number;}
const s: Scores = { ada: 10, alan: 12 };s.grace = 9; // ✅ any string key, number valueThe cost is honesty about absence: s.someone is typed number but is actually undefined at runtime. With the noUncheckedIndexedAccess compiler option, TypeScript makes this real by typing every index access as number | undefined — a strongly recommended setting.
Arrays vs tuples
Section titled “Arrays vs tuples”An array is a homogeneous, variable-length list: number[] is “any number of numbers.” A tuple is a fixed-length list where each position has its own type:
let list: number[] = [1, 2, 3, 4]; // any length, all numberlet point: [number, number] = [10, 20]; // exactly two numberslet pair: [string, number] = ["age", 36]; // position 0 string, position 1 numberTuples power patterns like React’s useState return ([value, setter]) and named-ish coordinates. They also support labels (documentation only, no runtime effect) and rest elements:
type HttpResult = [status: number, body: string];type Args = [first: string, ...rest: number[]];flowchart TB arr["number[] any length · all same type"] --> arrEx["[1, 2, 3, 4, ...]"] tup["[string, number] fixed length · per-position type"] --> tupEx["['age', 36]"]
readonly arrays and tuples
Section titled “readonly arrays and tuples”readonly number[] (or ReadonlyArray<number>) forbids mutating methods like push and index assignment — great for parameters you promise not to change. And recall from the last lesson: as const on an array literal produces exactly a readonly tuple of literals:
const rgb = [255, 128, 0] as const;// type: readonly [255, 128, 0]
function paint(color: readonly number[]) { /* can't mutate color */ }Preferring readonly array parameters is a cheap, high-value habit: it prevents a whole class of accidental-mutation bugs and communicates ownership.