Array types

Type a list of values with T[] or Array<T>, and lock it against mutation with readonly T[].

Signature
type A = number[];                 // shorthand
type B = Array<number>;            // generic form, identical to A
type C = readonly number[];        // shorthand
type D = ReadonlyArray<number>;    // generic form, identical to C
  • Added in TypeScript 1.0

Parameters

ParameterTypeDescription
$TypeTThe element type. Every element of the array must be assignable to it; use a parenthesised union such as `(string | number)[]` for mixed contents.
$readonly modifieroptionalreadonly T[]Removes the mutating methods (`push`, `pop`, `splice`, `sort`, `reverse`) and forbids index assignment. Permitted only on the shorthand array and tuple syntax.

Errors

tsc reports "Property 'push' does not exist on type 'readonly number[]'" for mutation of a readonly array, "The type 'readonly T[]' is 'readonly' and cannot be assigned to the mutable type 'T[]'" for the wrong assignment direction, and "'readonly' type modifier is only permitted on array and tuple literal types" for `readonly Array<T>`.

Changelog

  • PHP 3.4The `readonly T[]` shorthand was added as an alternative to `ReadonlyArray<T>`, and `Readonly<T[]>` began producing `readonly T[]`.
  • PHP 4.1`noUncheckedIndexedAccess` makes every index read `T | undefined`, including on ordinary arrays.
  • PHP 5.2`Array.prototype.toSorted`, `toReversed`, `toSpliced` and `with` were typed on `readonly T[]` (with `lib` set to `esnext`), giving non-mutating alternatives that work on readonly arrays.

T[] and Array<T> are the same type written two ways; pick one and be consistent. The shorthand reads better for simple element types, and the generic form is easier to read when the element type is itself long — Array<{ id: string; tags: string[] }> beats trailing brackets after a closing brace.

readonly is the part worth learning

readonly T[] (identical to ReadonlyArray<T>) removes the mutating methods and blocks index assignment. Use it on every parameter you do not intend to modify: it documents intent and stops a helper from quietly sorting its caller's array in place, which is one of the more common real bugs in JavaScript code.

Assignability runs one way only. A T[] is assignable to a readonly T[], because promising not to mutate is safe. The reverse is rejected. That asymmetry is deliberate but it does propagate: make one parameter readonly and you often have to make its callees readonly too.

Watch the precedence

string | number[] is a string or an array of numbers. An array of either is (string | number)[]. Likewise readonly Array<T> is not valid syntax — the modifier only attaches to the bracket form.

Example

Try it yourself
Loading editor…
Press Run to execute the code.

When to use it

  • Mark a helper's array parameter `readonly` so it cannot sort or push into the caller's data.
  • Type an exported constant list so consumers cannot mutate shared module state.
  • Type a list of mixed values with a parenthesised union element type.
  • Prefer `Array<T>` where the element type is a long inline object literal and trailing brackets hurt readability.

Frequently asked questions

Is there any difference between `T[]` and `Array<T>`?

No, they are exactly the same type and compile identically. The choice is stylistic: most style guides prefer `T[]` for simple element types and `Array<T>` when the element type is a long inline object or another generic, where trailing brackets are hard to spot.

How do I stop a function from mutating the array I pass it?

Type the parameter `readonly T[]` (or `ReadonlyArray<T>`). That removes `push`, `pop`, `splice`, `sort` and `reverse` from the parameter's type and blocks index assignment, while still accepting an ordinary array at the call site.

Why can't I assign a `readonly` array to a normal array variable?

Because it would let you mutate through the new reference, defeating the readonly guarantee. Assignability goes one way: `T[]` to `readonly T[]` is fine, the reverse is an error. Copy it with `slice()` or a spread if you genuinely need a mutable version.

More examples

readonly parameters and union precedence

Prints [4, 6, 10, 14, 22], then "source unchanged: 2,3,5,7,11", then the two mixed values. `map`, `filter` and `slice` all return new mutable arrays, so a readonly input does not infect the output. The last pair shows the precedence trap: `mixed` is an array holding strings and numbers, while `eitherStringOrNumbers` is either a single string or an array of numbers.

Example · typescript
const PRIMES: readonly number[] = [2, 3, 5, 7, 11];

function doubled(values: readonly number[]): number[] {
  return values.map((n) => n * 2);   // map returns a fresh mutable array
  // values.push(13);                // tsc: 'push' does not exist on readonly number[]
}

console.log(doubled(PRIMES));
console.log("source unchanged:", PRIMES.join(","));

// Precedence: parentheses matter.
const mixed: (string | number)[] = ["a", 1];
const eitherStringOrNumbers: string | number[] = [1, 2];
console.log(mixed, eitherStringOrNumbers);

`T[]` versus `Array<T>` in generic positions

Prints "admin editor" then "none". The two declarations describe exactly the same type. In `firstTag`, `readonly T[]` accepts both mutable arrays without a cast, because `T[]` is assignable to `readonly T[]` — the reverse would not be. The optional chain plus `??` is what a correct read looks like once `noUncheckedIndexedAccess` is on.

Example · typescript
type User = { id: string; tags: string[] };

// Same type, two spellings; the generic form reads better for inline objects.
const inline: Array<{ id: string; tags: string[] }> = [{ id: "u1", tags: ["admin"] }];
const aliased: User[] = [{ id: "u2", tags: ["editor", "beta"] }];

function firstTag<T extends { tags: readonly string[] }>(items: readonly T[]): string {
  return items[0]?.tags[0] ?? "none";
}

console.log(firstTag(inline), firstTag(aliased));
console.log(firstTag([]));

Discussion

  • Be the first to comment on this lesson.