06 · Arrays & Objects, Typed¶
🎥 Video walkthrough¶
TypeScript lets you describe exactly what's inside an array or object, so the compiler catches mismatched-shape bugs before you ever run the code.
Typed arrays¶
const scores: number[] = [90, 85, 77];
const names: Array<string> = ["Ada", "Grace"]; // equivalent generic syntax
scores.push(92); // fine -- number
// scores.push("x"); // error TS2345: Argument of type 'string' is not assignable to parameter of type 'number'
console.log(scores[0]); // 90
console.log(scores.length); // 4
number[] and Array<number> mean exactly the same thing — T[] is just
shorthand for Array<T>.
Tuples — fixed-length arrays with per-position types¶
let point: [number, number] = [3, 4];
let entry: [string, number] = ["Ada", 30];
console.log(point[0]); // 3, typed as number
console.log(entry[0]); // "Ada", typed as string
console.log(entry[1]); // 30, typed as number
// entry = [30, "Ada"]; // error TS2322: types don't match the declared order
A tuple looks like an array but tracks a specific type at each index —
useful for fixed-shape pairs/triples like coordinates or [key, value]
entries.
Typed objects (inline shape)¶
const person: { name: string; age: number } = {
name: "Ada",
age: 30,
};
console.log(person.name); // Ada
// person.email = "x"; // error TS2339: Property 'email' does not exist
Inline object types work, but for anything reused more than once, prefer a
named interface or type (covered in Module 5) instead of repeating the
inline shape everywhere.
Array methods keep their types¶
const numbers: number[] = [1, 2, 3, 4, 5];
const doubled: number[] = numbers.map((n) => n * 2);
const evens: number[] = numbers.filter((n) => n % 2 === 0);
const total: number = numbers.reduce((sum, n) => sum + n, 0);
console.log(doubled); // [2, 4, 6, 8, 10]
console.log(evens); // [2, 4]
console.log(total); // 15
.map, .filter, and .reduce infer their return types from the callback
you pass — TypeScript figures out doubled is number[] without you writing
it explicitly.
Object destructuring with types¶
interface Config {
host: string;
port: number;
debug?: boolean;
}
function connect({ host, port, debug = false }: Config): void {
console.log(`Connecting to ${host}:${port}, debug=${debug}`);
}
connect({ host: "localhost", port: 8080 });
// Connecting to localhost:8080, debug=false
Index signatures — objects with dynamic keys¶
const prices: { [productName: string]: number } = {
apple: 1.5,
banana: 0.75,
};
prices.cherry = 3.0; // adding a new key is fine -- shape matches the index signature
console.log(prices["apple"]); // 1.5
An index signature ({ [key: string]: number }) is how you type an object
you're using as a dictionary/map, where you don't know every key up front.
How It Actually Works¶
Array<T> (equivalently T[]) is a generic interface defined in TypeScript's own lib.es5.d.ts, with a member for every array method (push(item: T): number, map<U>(fn: (item: T, index: number, array: T[]) => U): U[], and so on). When you write const nums: number[] = [], the checker substitutes T = number into that library interface's declaration, and every subsequent method call is checked against the substituted signatures — nums.push("x") fails because the library declares push(...items: T[]): number, and with T bound to number, "x" isn't assignable to number. None of this exists at runtime; the compiled array is an ordinary JS array with no element-type tag, which is why (nums as any[]).push("x") compiles and runs fine (it just corrupts the array from the type system's point of view).
Index signatures ({ [key: string]: number }) tell the checker "any property access with a string key on this object has type number," which is a closed-world assumption the checker enforces only at the type level — it does not insert a runtime bounds or existence check. Accessing obj[key] where key isn't actually present at runtime returns undefined in JS, but the checker still reports its static type as number (not number | undefined) unless noUncheckedIndexedAccess is enabled, which makes every indexed access T | undefined to reflect the real runtime possibility of a missing key.
Object literal shapes are checked member-by-member the same way interfaces are (structural comparison), but array literals get their element type inferred as the union of all literal element types, then widened — [1, "a"] infers as (string | number)[], and every element access loses the connection to which specific element it was; the checker cannot narrow arr[0] to number just because you know positionally it came from 1, since ordinary arrays (unlike tuples) don't track per-index types.
Cheat sheet¶
| Feature | Syntax |
|---|---|
| Typed array | number[] or Array<number> |
| Tuple | [string, number] |
| Inline object type | { name: string; age: number } |
| Optional property | age?: number |
| Index signature | { [key: string]: number } |
| Destructure with default | function f({ x = 0 }: { x?: number }) {} |
🔀 See this in another language¶
Exercise¶
Define a tuple type Coordinate = [number, number]. Write a function
distance(a: Coordinate, b: Coordinate): number computing the Euclidean
distance between two points. Then write a function
summarizePrices(prices: { [item: string]: number }): { total: number;
average: number } that returns the total and average of all values in the
dictionary.