TypeScriptArrays

Arrays

Arrays are one of the most fundamental data structures in JavaScript, and TypeScript adds precise type information to every array — what types its elements can hold, whether it can be mutated, and more. This page covers everything from basic array types to advanced patterns like readonly arrays and tuple overlap.

Array Type Syntax

TypeScript offers two equivalent syntaxes for declaring array types. Both are correct; which you use is a matter of style.

TS
// Syntax 1: element type followed by []
let numbers: number[] = [1, 2, 3];
let names: string[] = ['Alice', 'Bob', 'Carol'];
let flags: boolean[] = [true, false, true];

// Syntax 2: Generic Array<T> form
let numbers2: Array<number> = [1, 2, 3];
let names2: Array<string> = ['Alice', 'Bob'];

// Both are interchangeable — pick one and stick with it
// The T[] form is more common in idiomatic TypeScript
Note
The Array<T> form is needed in some contexts where T[] would be ambiguous — for example, inside JSX where < might be confused with a JSX tag. In non-JSX files, T[] is generally preferred.
Type Inference for Arrays

TypeScript infers array types from their initial values, so explicit annotations are often unnecessary:

TS
// Inferred as number[]
const scores = [95, 87, 92, 78];

// Inferred as string[]
const fruits = ['apple', 'banana', 'cherry'];

// Inferred as (string | number)[] — mixed types widen to a union
const mixed = [1, 'two', 3];

// TypeScript protects the array from wrong element types
scores.push(100);     // OK
scores.push('A');     // Error: Argument of type 'string' is not assignable to type 'number'
Empty Arrays Need Annotations

If you initialize an empty array without an annotation, TypeScript infers its type as never[] until you push something in. It is best to annotate empty arrays explicitly:

TS
// Without annotation — inferred as never[]
const emptyBad = [];
emptyBad.push(1);     // Error: Argument of type 'number' is not assignable to type 'never'

// With annotation — clear and safe
const empty: number[] = [];
empty.push(1);   // OK

// Also valid
const empty2: Array<string> = [];
empty2.push('hello');  // OK
Common Array Methods — Typed

TypeScript knows the return types of every built-in array method. This means your callbacks get typed parameters automatically, and the return values are precise:

TS
const numbers = [1, 2, 3, 4, 5];

// map: (value: number) => string  →  string[]
const strings = numbers.map((n) => n.toString());
// strings: string[]

// filter: keeps the same element type
const evens = numbers.filter((n) => n % 2 === 0);
// evens: number[]

// find: returns T | undefined (might not find anything)
const found = numbers.find((n) => n > 3);
// found: number | undefined

// reduce: TypeScript infers from initialValue
const sum = numbers.reduce((acc, n) => acc + n, 0);
// sum: number

// forEach: returns void
numbers.forEach((n) => {
  console.log(n);  // n: number
});
Tip
When using reduce with a different accumulator type than the array element type, annotate the generic explicitly: numbers.reduce<string>((acc, n) => acc + n, "")
Multidimensional Arrays

Nested arrays are expressed by stacking the [] suffix:

TS
// 2D array — array of number arrays
const matrix: number[][] = [
  [1, 2, 3],
  [4, 5, 6],
  [7, 8, 9],
];

// Access is fully typed
const row: number[] = matrix[0];
const cell: number = matrix[1][2];  // 6

// 3D array
const cube: number[][][] = [[[1, 2], [3, 4]], [[5, 6], [7, 8]]];
Arrays of Objects

Arrays commonly hold objects. Define the object shape with an interface or type alias, then use it as the element type:

TS
interface User {
  id: number;
  name: string;
  email: string;
  active: boolean;
}

const users: User[] = [
  { id: 1, name: 'Alice', email: 'alice@example.com', active: true },
  { id: 2, name: 'Bob',   email: 'bob@example.com',   active: false },
];

// TypeScript knows each element is a User
const firstUser = users[0];        // User
const firstName = firstUser.name;  // string

// Array methods preserve the type
const activeUsers = users.filter((u) => u.active);
// activeUsers: User[]

const names = users.map((u) => u.name);
// names: string[]
Union Element Types

An array can hold multiple types when you use a union as the element type:

TS
// Array that can hold strings or numbers
const mixed: (string | number)[] = [1, 'two', 3, 'four'];

// Array that can hold null (useful for sparse data)
const nullable: (string | null)[] = ['Alice', null, 'Carol', null];

// Filtering narrows the type
const stringsOnly = mixed.filter((x): x is string => typeof x === 'string');
// stringsOnly: string[] — the type predicate narrows the result
Note
The (x): x is string syntax is a type predicate. It tells TypeScript that when the filter callback returns true, the element is a string. Without it, TypeScript would infer (string | number)[] even after filtering.
readonly Arrays

readonly arrays prevent mutation — no push, pop, splice, or index assignment. Use them when you want to guarantee an array is never modified:

TS
// Method 1: readonly prefix
const readonlyNums: readonly number[] = [1, 2, 3];

// Method 2: ReadonlyArray<T> generic
const readonlyNums2: ReadonlyArray<number> = [1, 2, 3];

// Method 3: as const (deepest immutability)
const readonlyNums3 = [1, 2, 3] as const;
// type: readonly [1, 2, 3] — a readonly tuple

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

TS
// Practical use: function that should not modify its input
function sum(numbers: readonly number[]): number {
  return numbers.reduce((acc, n) => acc + n, 0);
}

// Both mutable and readonly arrays can be passed in
sum([1, 2, 3]);                         // OK
const locked: readonly number[] = [1, 2, 3];
sum(locked);                            // OK — readonly is compatible
Tip
Prefer readonly arrays for function parameters when your function does not need to mutate the input. This signals intent clearly and prevents accidental mutations.
Destructuring Arrays with Types

Destructuring works exactly as in JavaScript — TypeScript infers each variable's type from the array element type:

TS
const rgb: [number, number, number] = [255, 128, 0];

// Destructuring a tuple — types are positional
const [red, green, blue] = rgb;
// red: number, green: number, blue: number

// Destructuring a regular array
const scores = [95, 87, 92];
const [first, second, ...rest] = scores;
// first: number, second: number, rest: number[]

// Default values
const [a = 0, b = 0] = [42];
// a: number (42), b: number (0)
Spread and Rest with Typed Arrays

TS
// Spread operator — works just like JS, types are merged
const a: number[] = [1, 2, 3];
const b: number[] = [4, 5, 6];
const combined: number[] = [...a, ...b];  // [1, 2, 3, 4, 5, 6]

// Rest parameters — collect all remaining args into a typed array
function sumAll(...nums: number[]): number {
  return nums.reduce((acc, n) => acc + n, 0);
}

sumAll(1, 2, 3, 4, 5);   // 15
sumAll();                  // 0 — empty rest param is valid

// Spread into a function call
const args: [number, number] = [10, 20];
Math.max(...args);   // OK
Array.from and Array Constructor

TS
// Array.from — TypeScript infers the element type from the callback
const numbers = Array.from({ length: 5 }, (_, i) => i + 1);
// type: number[]
// value: [1, 2, 3, 4, 5]

// From a Set
const set = new Set([1, 2, 3, 2, 1]);
const unique = Array.from(set);
// type: number[]
// value: [1, 2, 3]

// From a string (produces string[])
const chars = Array.from('hello');
// type: string[]
// value: ['h', 'e', 'l', 'l', 'o']
Sorting Typed Arrays

TS
// sort mutates the array and returns it
const nums: number[] = [3, 1, 4, 1, 5, 9];

// Always provide a comparator for number arrays
// Without one, JS converts to strings: [1, 1, 3, 4, 5, 9] (happens to be correct here)
// But [10, 9, 20] would sort as [10, 20, 9] without a comparator
nums.sort((a, b) => a - b);   // ascending
nums.sort((a, b) => b - a);   // descending

// Sorting objects
interface Product {
  name: string;
  price: number;
}

const products: Product[] = [
  { name: 'C', price: 30 },
  { name: 'A', price: 10 },
  { name: 'B', price: 20 },
];

products.sort((a, b) => a.price - b.price);
// Sorted by price ascending

products.sort((a, b) => a.name.localeCompare(b.name));
// Sorted alphabetically by name
Warning
Array.prototype.sort mutates the original array in place. If you need a sorted copy, spread first: [...original].sort(...). ES2023 added a non-mutating toSorted() method.
Typed Array Methods — Return Types Table

Method

Input

Return Type

map(fn)

T[]

U[] (inferred from fn)

filter(fn)

T[]

T[]

filter(predicate)

T[]

U[] (narrowed by predicate)

find(fn)

T[]

T | undefined

findIndex(fn)

T[]

number

reduce(fn, init)

T[]

typeof init

some(fn)

T[]

boolean

every(fn)

T[]

boolean

includes(val)

T[]

boolean

indexOf(val)

T[]

number

flat()

(T | T[])[]

T[]

flatMap(fn)

T[]

U[]

Practical Pattern: Processing an Array of API Results

TS
interface ApiResponse {
  id: number;
  title: string;
  completed: boolean;
  userId: number;
}

async function fetchTodos(): Promise<ApiResponse[]> {
  const response = await fetch('https://jsonplaceholder.typicode.com/todos');
  return response.json() as Promise<ApiResponse[]>;
}

async function main() {
  const todos: ApiResponse[] = await fetchTodos();

  // Filter to completed todos — still ApiResponse[]
  const done = todos.filter((t) => t.completed);

  // Extract titles — string[]
  const titles = done.map((t) => t.title);

  // Group by userId — Record<number, ApiResponse[]>
  const byUser = todos.reduce<Record<number, ApiResponse[]>>((acc, todo) => {
    const key = todo.userId;
    if (!acc[key]) acc[key] = [];
    acc[key].push(todo);
    return acc;
  }, {});
}
Summary
  • Use T[] or Array<T> syntax — both are equivalent, T[] is more common

  • TypeScript infers array types from initial values — annotate empty arrays explicitly

  • All built-in array methods return precise types — map, filter, find, reduce all work as expected

  • readonly number[] prevents mutation — use for function parameters that should not modify input

  • as const creates a deeply readonly tuple with narrowest literal types

  • Type predicates in filter callbacks narrow the resulting array type

  • Always provide a comparator to .sort() when sorting numbers or objects

  • Use Array.from() with a mapping function to create typed arrays of any length