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.

Create a free account to save this page, take notes and track your progress. Sign in

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[] or Array<number>. Methods such as map, filter and reduce carry types through, so scores.filter((s) => s > 50) is still number[].
  • 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[] (or ReadonlyArray<number>) forbids push, sort in 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 const becomes readonly ["admin", "editor"], and its values become literal types.
  • typeof and 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

Tuples, readonly arrays and as constTry it yourself
// 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 readonly to freeze at runtime. It is a compile-time promise; use Object.freeze if you also need a runtime guarantee.
  • Forgetting as const. Without it { A: 90 } is { A: number } and Grade collapses to string.
  • Casting Object.keys results. Object.keys returns string[] by design; the as 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 const keeps exact literal types, and keyof 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.

Was this page helpful?