Type expression

Readonly arrays

  • Collections
  • Tested with TypeScript 5.9

A read-only array type, {readonly string[]} or {ReadonlyArray<string>}, has every array method that does not change the array (map, filter, slice, indexing) and none that does (push, splice, sort, index assignment). Calling one is an error (TS2339), as the first example shows.

It is most useful on parameters. @param {readonly string[]} sizes tells callers that the function will not modify their array, and TypeScript holds the function to it. Ordinary arrays can be passed where a readonly array is expected.

At a glance

Use JSDoc in a .js file TypeScript in a .ts file
A read-only array /** @param {readonly string[]} sizes */ (sizes: readonly string[])
Generic spelling /** @param {ReadonlyArray<string>} sizes */ (sizes: ReadonlyArray<string>)
A read-only tuple /** @type {readonly [number, number]} */ : readonly [number, number]

Examples

sizes.js Error confirmed 5.9.3 TypeScript 5.9.3, run 21 October 2026: sizes.js(4,9): error TS2339: Property 'push' does not exist on type 'readonly string[]'. 6.0.3 TypeScript 6.0.3, run 21 October 2026: the same as 5.9.3. sizes.js(4,9): error TS2339: Property 'push' does not exist on type 'readonly string[]'. 7.0.2 TypeScript 7.0.2, run 21 October 2026: the same as 5.9.3. sizes.js(4,9): error TS2339: Property 'push' does not exist on type 'readonly string[]'. checked 21 October 2026
/** @param {readonly string[]} sizes */
export function withXL(sizes) {
  sizes.push("XL");error TS2339: Property 'push' does not exist on type 'readonly string[]'.
  return sizes;
}
A readonly array has no push().
jsconfig.json for this example
{
  "compilerOptions": {
    "checkJs": true,
    "noEmit": true,
    "strict": true,
    "target": "es2022",
    "module": "nodenext",
    "types": []
  },
  "include": ["*.js"]
}
sizes.js sizes.ts TypeScript 5.9.3 TypeScript 5.9.3, run 21 October 2026: No errors. 6.0.3 TypeScript 6.0.3, run 21 October 2026: the same as 5.9.3. No errors. 7.0.2 TypeScript 7.0.2, run 21 October 2026: the same as 5.9.3. No errors. checked 21 October 2026
/**
 * @param {readonly string[]} sizes
 * @returns {string[]}
 */
export function sortedSizes(sizes) {
  return [...sizes].sort();
}

/** @param {ReadonlyArray<number>} prices */
export function cheapest(prices) {
  return Math.min(...prices);
}

const sizes = ["L", "S", "M"];
sortedSizes(sizes);
cheapest([19.5, 12]);
export function sortedSizes(sizes: readonly string[]): string[] {
  return [...sizes].sort();
}

export function cheapest(prices: ReadonlyArray<number>) {
  return Math.min(...prices);
}

const sizes = ["L", "S", "M"];
sortedSizes(sizes);
cheapest([19.5, 12]);
Taking readonly arrays and returning new ones.
jsconfig.json for this example
{
  "compilerOptions": {
    "checkJs": true,
    "noEmit": true,
    "strict": true,
    "target": "es2022",
    "module": "nodenext",
    "types": []
  },
  "include": ["*.js"]
}

Working with them

To sort or extend, copy first: [...sizes].sort() returns a new, mutable string[]. Methods that already return new arrays, such as map and filter, work directly.

TypeScript added the readonly array syntax in version 3.4. A const assertion on an array literal, /** @type {const} */ (["S", "M"]), produces a read-only tuple.

Gotchas

  • Read-only exists only in the types: TypeScript checks it, the running code does not. Object.freeze(array) makes it read-only at run time as well, and TypeScript types the result as readonly T[].
  • A readonly string[] cannot be passed where a mutable string[] is expected, because the receiver might change it. Mark parameters readonly from the bottom up.

Questions

What is the difference between readonly string[] and ReadonlyArray<string>?

None; they are two spellings of the same type.

Sources

Last reviewed 20 October 2026.