Arrays, Tuples & Readonly Arrays

Reviewed & published by Brayan K

An array (T[] or Array<T>) holds many values of one type, a tuple ([string, number]) is a fixed-length list with a type per position, and readonly versions prevent mutation.

Part of the free TypeScript course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.

What You'll Learn

💡 Real-World Analogy

An array is a shopping list — any number of items, all "things to buy". A tuple is a form field row: slot 1 is always the name, slot 2 always the age, in that exact order and never longer. A readonly array is a laminated reference card — you can read it all day, but you can't scribble new entries on it; to change anything you print a fresh card.

1. Arrays: T[] vs Array<T>

An array holds any number of values of a single type. You can write the type as number[] or as the generic Array<number> — they're identical. The payoff is that array methods preserve the element type: map over a number[] gives a number[], and trying to push a value of the wrong type is a compile error.

let scores: number[] = [95, 87, 92];
let scores2: Array<number> = [95, 87, 92]; // identical type

const doubled = scores.map(n => n * 2); // doubled: number[]
// scores.push("oops"); // ❌ 'string' is not assignable to 'number'
// An ARRAY holds any number of values of the SAME type.
// Two IDENTICAL ways to write the type:
//   let scores: number[] = [95, 87];
//   let scores: Array<number> = [95, 87];
// They mean exactly the same thing — pick whichever reads better to you.

let scores = [95, 87, 92];
console.log("scores:", scores);          // scores: [ 95, 87, 92 ]
console.log("first:", scores[0]);        // first: 95
console.log("length:", scores.length);   // length: 3

scores.push(100);                        // adding another number is fine
console.log("after push:", scores);      // after push: [ 95, 87, 92, 100 ]

// Array methods keep the element type:
const doubled = scores.map((n) => n * 2);
console.log("doubled:", doubled);        // doubled: [ 190, 174, 184, 200 ]

const passing = scores.filter((n) => n >= 90);
console.log("passing:", passing);        // passing: [ 95, 92, 100 ]

// In a .ts file:  let scores: number[] = [...]   (or Array<number>)
//   scores.push("oops"); // ❌ Argument of type 'string' not assignable to 'number'

2. Tuples & Named Members

A tuple is a fixed-length array where each position has its own type: [string, number] means "a string then a number, exactly two elements". Named tuple members ([x: number, y: number]) attach labels purely for readability — they don't change the runtime value. Tuples are ideal for fixed pairs and for returning several related values from one function.

let user: [string, number] = ["Ada", 36];
// user = [36, "Ada"]; // ❌ order matters — types must line up

type Point = [x: number, y: number]; // named members (labels only)

function divmod(a: number, b: number): [number, number] {
  return [Math.floor(a / b), a % b];
}
const [q, r] = divmod(17, 5); // q: number, r: number
// A TUPLE is a FIXED-LENGTH array with a type for EACH position.
// In TypeScript:  let user: [string, number] = ["Ada", 36];
// Position 0 must be a string, position 1 must be a number.

let user = ["Ada", 36];
console.log("name:", user[0]);   // name: Ada   (the string slot)
console.log("age: ", user[1]);   // age:  36    (the number slot)

// NAMED TUPLE MEMBERS add labels for readability (names are TS-only):
//   type Point = [x: number, y: number];
let point = [10, 20];
console.log("x =", point[0], "y =", point[1]); // x = 10 y = 20

// Tuples are perfect for fixed pairs — like a key/value or a return of two things:
function divmod(a, b) {
  return [Math.floor(a / b), a % b];  // [quotient, remainder]
}
const [q, r] = divmod(17, 5);
console.log("quotient:", q, "remainder:", r); // quotient: 3 remainder: 2

// In a .ts file:
//   let user: [string, number] = ["Ada", 36];
//   type Point = [x: number, y: number];
//   function divmod(a: number, b: number): [number, number] { ... }

3. Readonly Arrays/Tuples & Rest Elements

Prefix with readonly to forbid mutation: readonly number[] (or ReadonlyArray<number>) removes push, pop, and index assignment at the type level, so you build a new array instead of changing the old one. A rest element in a tuple — [command: string, ...flags: string[]] — gives a fixed start followed by a flexible, typed tail.

const a: readonly number[] = [1, 2, 3];
// a.push(4);     // ❌ Property 'push' does not exist on 'readonly number[]'
const bigger = [...a, 4]; // ✅ build a NEW array

const t: readonly [string, number] = ["Ada", 36];
// t[1] = 0;      // ❌ Cannot assign to '1' because it is a read-only property

type Args = [command: string, ...flags: string[]]; // rest element in a tuple
// READONLY arrays/tuples can't be mutated (no push/pop/assignment) — a
// compile-time guarantee. In TypeScript:
//   const a: readonly number[] = [1, 2, 3];
//   const t: readonly [string, number] = ["Ada", 36];
// At runtime they're normal arrays; readonly is enforced by the COMPILER.
// (a.push(4) would be a TS error: "Property 'push' does not exist on readonly...")

const config = [200, 404, 500];
console.log("can still READ:", config[1]); // can still READ: 404
const next = [...config, 503];             // build a NEW array instead of mutating
console.log("new array:", next);           // new array: [ 200, 404, 500, 503 ]
console.log("original untouched:", config);// original untouched: [ 200, 404, 500 ]

// REST elements in a tuple capture "the rest" with a known leading shape:
//   type Args = [command: string, ...flags: string[]];
function run(command, ...flags) {
  return command + " [" + flags.join(", ") + "]";
}
console.log(run("build"));                  // build []
console.log(run("build", "--fast", "--ci"));// build [--fast, --ci]

// In a .ts file:
//   const a: readonly number[] = [1, 2, 3];
//   type Args = [command: string, ...flags: string[]];

🎯 Your Turn

Write labelled, which reads a [label, value] tuple. Fill in the two blanks marked ___, then run it.

// 🎯 YOUR TURN — replace each ___ then press "Try it Yourself".
// 'labelled' takes a tuple [label, value] and returns "label: value".
// In TS:  function labelled(pair: [string, number]): string

function labelled(pair) {
  const label = pair[___];   // 👉 index 0 — the string
  const value = pair[___];   // 👉 index 1 — the number
  return label + ": " + value;
}

console.log(labelled(["score", 95]));  // score: 95
console.log(labelled(["age", 36]));    // age: 36

// And an array of numbers — sum them:
const nums = [1, 2, 3, 4];
console.log("sum:", nums.reduce((a, b) => a + b, 0)); // sum: 10

// ✅ Expected output:
//    score: 95
//    age: 36
//    sum: 10

Common Errors (and the fix)

Pro Tips

Mini-Challenge: Point Toolkit

No blanks this time — just a brief and a starting outline. Build the tuple-based point helpers yourself, run it, and check your output against the example in the comments.

// 🎯 MINI-CHALLENGE: an (x, y) point toolkit using tuples
// In TypeScript:  type Point = [x: number, y: number];
//
// 1. Write addPoints(a, b) that returns a NEW tuple [a.x + b.x, a.y + b.y]
//    (use index 0 for x and index 1 for y).
// 2. Write distanceFromOrigin(p) = Math.hypot(p[0], p[1]).
// 3. Log:
//      addPoints([1, 2], [3, 4])         -> [ 4, 6 ]
//      distanceFromOrigin([3, 4])        -> 5
//      const [x, y] = addPoints([0, 0], [5, 12]); then  distanceFromOrigin([x, y])  -> 13
//
// ✅ Expected output:
//    [ 4, 6 ]
//    5
//    13

// your code here

🎉 Lesson Complete

Practice quiz

How are number[] and Array<number> related?

  • They are completely different types
  • Array<number> allows mixed types
  • They are exactly the same type, two syntaxes
  • number[] is a tuple, Array<number> is not

Answer: They are exactly the same type, two syntaxes. number[] and Array<number> are identical - just two ways to write the same array type. Pick one and stay consistent.

What does the tuple type [string, number] describe?

  • Exactly two elements: a string then a number
  • An array of strings and numbers in any order
  • An object with string and number keys
  • A union of string and number

Answer: Exactly two elements: a string then a number. A tuple is a fixed-length array with a type per position: index 0 must be a string, index 1 must be a number.

When you map over a number[], what is the type of the result?

  • any[]
  • unknown[]
  • object[]
  • number[]

Answer: number[]. Array methods preserve the element type, so mapping a number[] gives a number[] (assuming the callback returns numbers).

What do named tuple members like [x: number, y: number] add?

  • Runtime property names on the array
  • Labels for readability only - no runtime change
  • Automatic validation of values
  • A conversion to an object

Answer: Labels for readability only - no runtime change. Named tuple members attach labels purely for readability and editor hints; they don't change the runtime value at all.

What does readonly do to an array at the type level?

  • Removes mutating methods like push and index assignment
  • Freezes the array at runtime
  • Makes every element a string
  • Limits the array to three elements

Answer: Removes mutating methods like push and index assignment. readonly removes push, pop, splice, and index assignment at the type level - a compile-time guarantee. The runtime array is still mutable.

How should you change a readonly number[] to add an element?

  • Call .push() - it works on readonly arrays
  • Cast it to any first
  • Build a new array with the spread operator
  • Use the delete keyword

Answer: Build a new array with the spread operator. You can't mutate a readonly array; build a new one instead, e.g. const bigger = [...a, 4].

What does a rest element in a tuple like [command: string, ...flags: string[]] mean?

  • Exactly two strings
  • A fixed leading string then any number of trailing strings
  • An array that can never grow
  • A tuple of unknown length only

Answer: A fixed leading string then any number of trailing strings. A rest element gives a fixed, typed start followed by a flexible typed tail - here one string then any number of string flags.

Why would you use a tuple instead of a plain array?

  • When you have any number of same-typed items
  • Tuples are always faster at runtime
  • Arrays cannot hold objects
  • When the length is fixed and each position has a specific meaning

Answer: When the length is fixed and each position has a specific meaning. Use a tuple when length is fixed and each slot means something (like [x, y]); use an array for any number of same-typed items.

For a tuple typed [string, number], why does [36, "Ada"] cause an error?

  • Tuples can't hold numbers
  • The element types are in the wrong order
  • Tuples must have at least three elements
  • Numbers must come last in every tuple

Answer: The element types are in the wrong order. Order matters in a tuple: position 0 must be a string and position 1 a number, so the values must line up with their declared types.

Which type is identical to readonly number[]?

  • Array<number>
  • number[]
  • ReadonlyArray<number>
  • Readonly<number>

Answer: ReadonlyArray<number>. readonly number[] and ReadonlyArray<number> are two spellings of the same read-only array type.

Continue this course

Frequently asked questions

Is T[] different from Array<T>?

No — number[] and Array<number> are exactly the same type, just two syntaxes. T[] is shorter and the most common; Array<T> reads more like other generics and can be clearer when the element type itself is complex. Pick one style and stay consistent within a file or project.

When should I use a tuple instead of an array?

Use a tuple when the length is fixed and each position has a specific meaning and type — an (x, y) coordinate, an [key, value] pair, or a function returning two related things. Use a plain array when you have any number of same-typed items. Tuples document the exact shape; arrays document 'a list of'.

What does 'readonly' do to an array or tuple?

It removes the mutating methods (push, pop, splice, index assignment) at the type level, so the compiler stops you changing the array. The runtime array is still mutable — readonly is a compile-time contract. To genuinely change a readonly array, build a NEW one with the spread operator instead of mutating it.

Can tuples have a variable number of elements?

Yes, via a rest element. A tuple like [command: string, ...flags: string[]] requires a leading string and then allows any number of trailing strings. This gives you a fixed, typed beginning with a flexible tail — perfect for command-and-arguments style signatures.

Related lessons