TypeScript Fundamentals: Types, Generics and Real-World Patterns · TypeScript Basics: Types, Narrowing and Functions
Arrays, Tuples, Readonly Data and as const
Type arrays and tuples, protect data with readonly, use as const to infer exact literal types, and replace enums with literal unions and lookup objects.
What you'll build
A score tracker: typed arrays of results, a labelled tuple for [name, score] rows, a read-only configuration object whose values become exact types with as const, and a type-safe lookup built from it.
The idea
- Arrays:
number[]orArray<number>. Methods such asmap,filterandreducecarry types through, soscores.filter((s) => s > 50)is stillnumber[]. - Tuples: fixed-length arrays whose positions have their own types:
[string, number]. Label them for readability:[name: string, score: number]. Use them for returning pairs and for fixed records. readonly:readonly number[](orReadonlyArray<number>) forbidspush,sortin place and index assignment. Accept read-only arrays in functions that don't change them; callers can pass either.as const: freezes a literal's type.["admin", "editor"] as constbecomesreadonly ["admin", "editor"], and its values become literal types.typeofand indexed access turn such a value into a type:typeof ROLES[number]is"admin" | "editor".
This gives a lightweight alternative to enum: a const object or array is plain JavaScript, easy to iterate, and its union type stays in sync automatically.
The code
// A labelled tuple: position 0 is the name, position 1 the score
type Row = [name: string, score: number];
const rows: Row[] = [
["Ada", 92],
["Grace", 88],
["Linus", 71],
];
// Destructuring a tuple keeps each variable typed
const [topName, topScore] = rows[0];
console.log(`Top: ${topName} with ${topScore}`);
// Accepting readonly means the function promises not to change the array
function average(scores: readonly number[]): number {
return scores.reduce((sum, s) => sum + s, 0) / scores.length;
}
console.log("Average:", average(rows.map(([, score]) => score)).toFixed(1));
const frozen: readonly number[] = [1, 2, 3];
// @ts-expect-error: push does not exist on a readonly array
frozen.push(4);
// as const: exact literal types instead of string and number
const GRADES = {
A: 90,
B: 80,
C: 70,
} as const;
type Grade = keyof typeof GRADES; // "A" | "B" | "C"
type Threshold = (typeof GRADES)[Grade]; // 90 | 80 | 70
function gradeFor(score: number): Grade | "F" {
for (const grade of Object.keys(GRADES) as Grade[]) {
if (score >= GRADES[grade]) return grade;
}
return "F";
}
for (const [name, score] of rows) {
console.log(`${name.padEnd(6)} ${score} -> ${gradeFor(score)}`);
}
const ROLES = ["admin", "editor", "viewer"] as const;
type Role = (typeof ROLES)[number]; // "admin" | "editor" | "viewer"
const role: Role = "editor";
console.log(role, ROLES.length, ROLES.includes("admin"));
Output
Top: Ada with 92 Average: 83.7 Ada 92 -> A Grace 88 -> B Linus 71 -> C editor 3 true
Common mistakes
- Using tuples for long records. After three or four positions, an object with named properties is clearer.
- Mutating an array you received. Take
readonly T[]when you only read, and copy with[...items]before sorting. - Expecting
readonlyto freeze at runtime. It is a compile-time promise; useObject.freezeif you also need a runtime guarantee. - Forgetting
as const. Without it{ A: 90 }is{ A: number }andGradecollapses tostring. - Casting
Object.keysresults.Object.keysreturnsstring[]by design; theas Grade[]cast above is a deliberate, local exception.
Key points
- Arrays are
T[]; tuples are fixed-length arrays with typed, optionally labelled positions. readonly T[]documents and enforces that a function won't modify its input.as constkeeps exact literal types, andkeyof typeof,typeof x[number]derive unions from values.- A const object or array plus a derived union often replaces an
enum.
Further reading: The Handbook: Everyday Types.
Practice questions
What does `as const` do to `["admin", "editor"]`?
Answer
It makes the array readonly and gives it a tuple type of exact literals, readonly ["admin", "editor"], instead of string[]. You can then derive the union "admin" | "editor" with (typeof ROLES)[number].
Why accept `readonly number[]` in a function that only reads the array?
Answer
It documents that the function will not modify its input and lets callers pass either mutable or readonly arrays, whereas a number[] parameter would reject readonly arrays.
Open in the app to mark lessons complete, take notes and track your progress.