TypeScript

Arrays & Tuples in TypeScript

20 min Lesson 4 of 40

Working with Arrays in TypeScript

Arrays are fundamental data structures in TypeScript, just as they are in JavaScript. However, TypeScript adds type safety to arrays, ensuring that all elements are of the expected type. This prevents many common runtime errors and makes your code more predictable.

Typed Arrays

Basic Array Syntax

There are two ways to declare typed arrays in TypeScript:

// Method 1: Type followed by []
let numbers: number[] = [1, 2, 3, 4, 5];
let names: string[] = ["Alice", "Bob", "Charlie"];
let flags: boolean[] = [true, false, true];

// Method 2: Generic Array type
let numbers: Array<number> = [1, 2, 3, 4, 5];
let names: Array<string> = ["Alice", "Bob", "Charlie"];
let flags: Array<boolean> = [true, false, true];

Preferred Style: Most TypeScript developers prefer the type[] syntax over Array<type> because it's more concise and easier to read. However, both are functionally equivalent.

Type Inference with Arrays

TypeScript can infer array types from their initial values:

// TypeScript infers these as typed arrays
let numbers = [1, 2, 3];           // Inferred as number[]
let names = ["Alice", "Bob"];      // Inferred as string[]
let mixed = [1, "two", true];      // Inferred as (string | number | boolean)[]

// Empty arrays default to any[]
let empty = [];                    // Inferred as any[]
empty.push(1);                     // OK
empty.push("hello");               // OK - not ideal!

// Better: Explicitly type empty arrays
let numbers: number[] = [];        // Typed as number[]
numbers.push(1);                   // OK
numbers.push("hello");             // Error!

Array Operations with Type Safety

TypeScript ensures type safety for all array operations:

let numbers: number[] = [1, 2, 3];

// Adding elements
numbers.push(4);           // OK
numbers.push("5");         // Error: Argument of type 'string' is not assignable to parameter of type 'number'

// Accessing elements
let first = numbers[0];    // Type: number
let last = numbers[numbers.length - 1];  // Type: number

// Array methods maintain type safety
let doubled = numbers.map(n => n * 2);        // Type: number[]
let strings = numbers.map(n => n.toString()); // Type: string[]
let evens = numbers.filter(n => n % 2 === 0); // Type: number[]

// Reducing
let sum = numbers.reduce((acc, n) => acc + n, 0);  // Type: number

Multidimensional Arrays

You can create arrays of arrays for multidimensional data:

// 2D array (matrix)
let matrix: number[][] = [
    [1, 2, 3],
    [4, 5, 6],
    [7, 8, 9]
];

// Accessing elements
let element = matrix[0][1];  // 2

// 3D array
let cube: number[][][] = [
    [[1, 2], [3, 4]],
    [[5, 6], [7, 8]]
];

// Array of objects
interface User {
    name: string;
    age: number;
}

let users: User[] = [
    { name: "Alice", age: 30 },
    { name: "Bob", age: 25 }
];

Readonly Arrays

TypeScript provides readonly arrays that cannot be modified after creation:

// Readonly array using ReadonlyArray type
let numbers: ReadonlyArray<number> = [1, 2, 3];

numbers[0] = 10;           // Error: Index signature in type 'readonly number[]' only permits reading
numbers.push(4);           // Error: Property 'push' does not exist on type 'readonly number[]'
numbers.pop();             // Error: Property 'pop' does not exist on type 'readonly number[]'

// Readonly array using readonly modifier (preferred)
let names: readonly string[] = ["Alice", "Bob"];

names[0] = "Charlie";      // Error
names.push("David");       // Error

// You can still read from readonly arrays
console.log(numbers[0]);   // OK
console.log(numbers.length);  // OK
let copy = [...numbers];   // OK - creates a mutable copy

Use Case: Readonly arrays are useful when you want to ensure that data isn't accidentally modified, especially when passing arrays to functions or sharing them across different parts of your application.

Function Parameters with Readonly Arrays

// Function that doesn't modify the array
function sum(numbers: readonly number[]): number {
    // numbers.push(0);  // Error - can't modify readonly array
    return numbers.reduce((acc, n) => acc + n, 0);
}

// Can be called with both mutable and readonly arrays
let mutable = [1, 2, 3];
let immutable: readonly number[] = [4, 5, 6];

console.log(sum(mutable));    // OK
console.log(sum(immutable));  // OK

Understanding Tuples

Tuples are arrays with a fixed length and specific types for each position. They're perfect for representing structured data where each position has a specific meaning.

Basic Tuple Syntax

// Tuple with two elements
let person: [string, number] = ["Alice", 30];

// Accessing tuple elements
let name = person[0];     // Type: string
let age = person[1];      // Type: number

// Error: wrong types
let invalid: [string, number] = [30, "Alice"];  // Error: Type 'number' is not assignable to type 'string'

// Error: wrong length
let tooShort: [string, number] = ["Alice"];     // Error: Type '[string]' is not assignable to type '[string, number]'
let tooLong: [string, number] = ["Alice", 30, "extra"];  // Error

Real-World Tuple Examples

// Coordinates
type Point = [number, number];
let origin: Point = [0, 0];
let point: Point = [10, 20];

// RGB color
type RGB = [number, number, number];
let red: RGB = [255, 0, 0];
let blue: RGB = [0, 0, 255];

// HTTP response
type Response = [number, string];
let success: Response = [200, "OK"];
let notFound: Response = [404, "Not Found"];

// Database record
type User = [number, string, string, boolean];  // [id, name, email, isActive]
let user: User = [1, "Alice", "alice@example.com", true];

Optional Tuple Elements

Tuple elements can be optional using the ? modifier:

// Tuple with optional element
type User = [string, number, string?];  // name, age, email (optional)

let user1: User = ["Alice", 30, "alice@example.com"];  // OK
let user2: User = ["Bob", 25];                         // OK - email is optional
let user3: User = ["Charlie"];                         // Error - age is required

// Optional elements must come after required ones
type Invalid = [string?, number];  // Error: A required element cannot follow an optional element

Rest Elements in Tuples

Tuples can have rest elements for variable-length sections:

// Tuple with rest element
type StringNumberBooleans = [string, number, ...boolean[]];

let valid1: StringNumberBooleans = ["hello", 42];
let valid2: StringNumberBooleans = ["hello", 42, true];
let valid3: StringNumberBooleans = ["hello", 42, true, false, true];

// Rest element can be in the middle
type Numbers = [number, ...number[], number];
let nums: Numbers = [1, 2, 3, 4, 5];  // First and last must be numbers

// Multiple types in rest
type Mixed = [string, ...(number | boolean)[]];
let mixed: Mixed = ["start", 1, true, 2, false];

Tuple Labels

TypeScript 4.0+ allows labeling tuple elements for better readability:

// Without labels
type Point = [number, number];

// With labels (more descriptive)
type Point = [x: number, y: number];

// Complex example
type User = [
    id: number,
    name: string,
    email: string,
    isActive: boolean
];

let user: User = [1, "Alice", "alice@example.com", true];

// Labels don't change runtime behavior, just improve readability
// You still access by index
console.log(user[0]);  // 1
console.log(user[1]);  // "Alice"

When to Use Labels: Use tuple labels when the meaning of each position isn't obvious from the types alone. They serve as inline documentation and improve code readability.

Readonly Tuples

Just like arrays, tuples can be readonly:

// Readonly tuple
let point: readonly [number, number] = [10, 20];

point[0] = 5;  // Error: Cannot assign to '0' because it is a read-only property

// Readonly with labels
type Point = readonly [x: number, y: number];
let origin: Point = [0, 0];
// origin[0] = 1;  // Error

// Function accepting readonly tuple
function distance(point: readonly [number, number]): number {
    return Math.sqrt(point[0] ** 2 + point[1] ** 2);
}

Destructuring Arrays and Tuples

TypeScript maintains type safety during destructuring:

// Array destructuring
let numbers = [1, 2, 3, 4, 5];
let [first, second, ...rest] = numbers;
// first: number, second: number, rest: number[]

// Tuple destructuring
type Point = [number, number];
let point: Point = [10, 20];
let [x, y] = point;
// x: number, y: number

// Labeled tuple destructuring
type User = [id: number, name: string, email: string];
let user: User = [1, "Alice", "alice@example.com"];
let [id, name, email] = user;
// id: number, name: string, email: string

// Skipping elements
let [, , third] = numbers;  // third: number

// Default values
let [a, b, c = 0] = [1, 2];
// a: number, b: number, c: number (defaults to 0)

Spread Operator with Arrays and Tuples

// Spreading arrays
let arr1 = [1, 2, 3];
let arr2 = [4, 5, 6];
let combined = [...arr1, ...arr2];  // Type: number[]

// Spreading tuples
type Point2D = [number, number];
type Point3D = [number, number, number];

let point2D: Point2D = [10, 20];
let point3D: Point3D = [...point2D, 30];  // OK

// Mixing types
let mixed = [...arr1, "hello", ...arr2];  // Type: (string | number)[]

// Spreading in function calls
function sum(a: number, b: number, c: number): number {
    return a + b + c;
}

let numbers: [number, number, number] = [1, 2, 3];
sum(...numbers);  // OK - tuple spread matches parameter count

When to Use Arrays vs Tuples

Use Arrays when:

  • All elements have the same type
  • The length is variable or unknown
  • You need array methods like map, filter, reduce
  • You're working with collections of similar items

Use Tuples when:

  • You have a fixed number of elements
  • Each position has a specific meaning and possibly different type
  • You're representing structured data (like coordinates, database records)
  • You need to return multiple values from a function

Practical Examples

Example 1: Function Returning Multiple Values

// Using tuple to return multiple values
function getMinMax(numbers: number[]): [number, number] {
    let min = Math.min(...numbers);
    let max = Math.max(...numbers);
    return [min, max];
}

let [min, max] = getMinMax([5, 2, 8, 1, 9]);
console.log(`Min: ${min}, Max: ${max}`);

Example 2: Type-Safe State Management

// Similar to React's useState
type State<T> = [T, (newValue: T) => void];

function useState<T>(initialValue: T): State<T> {
    let value = initialValue;

    function setValue(newValue: T) {
        value = newValue;
    }

    return [value, setValue];
}

let [count, setCount] = useState(0);
setCount(5);  // OK
// setCount("5");  // Error: Argument of type 'string' is not assignable to parameter of type 'number'

Example 3: Database Query Result

// Typed database row
type UserRow = [
    id: number,
    username: string,
    email: string,
    createdAt: Date,
    isActive: boolean
];

function getUserById(id: number): UserRow | null {
    // Simulated database query
    return [1, "alice", "alice@example.com", new Date(), true];
}

let user = getUserById(1);
if (user) {
    let [id, username, email, createdAt, isActive] = user;
    console.log(`${username} (${email})`);
}

Practice Exercise

Create a file called arrays-tuples-practice.ts and complete these tasks:

  1. Create a typed array of numbers and use map, filter, and reduce to:
    • Double all numbers
    • Filter out numbers less than 10
    • Sum the remaining numbers
  2. Create a function that takes a readonly array of strings and returns the longest string
  3. Create a tuple type for a book: [title: string, author: string, year: number, isAvailable?: boolean], then create an array of books
  4. Write a function that takes coordinates as a tuple [x: number, y: number] and returns the distance from origin
  5. Create a function that returns both the result and error of an operation as a tuple: [result: T | null, error: Error | null]
  6. Implement a function that splits an array into chunks:
    function chunk<T>(array: T[], size: number): T[][] {
        // Your implementation
    }
    
    console.log(chunk([1, 2, 3, 4, 5], 2));
    // Output: [[1, 2], [3, 4], [5]]

Bonus Challenge: Create a type-safe CSV parser that returns an array of tuples, where each tuple represents a row with typed columns.

Summary

  • Arrays in TypeScript are typed collections where all elements share the same type
  • Use type[] syntax for arrays (preferred) or Array<type>
  • Readonly arrays prevent modification and are useful for immutable data
  • Tuples are fixed-length arrays with specific types for each position
  • Tuple elements can be optional or use rest elements for variable sections
  • Tuple labels improve readability without changing runtime behavior
  • Destructuring maintains type safety for both arrays and tuples
  • Use arrays for collections of similar items, tuples for structured data with fixed positions
  • TypeScript enforces type safety for all array and tuple operations

Next Steps: In the next lesson, we'll explore enums, a powerful TypeScript feature for defining named constants and creating more expressive code.