skip to content

Array Types & readonly Arrays

The two array syntaxes, how element types are inferred from literals, and what readonly arrays buy you at the boundaries of a function. The classic follow-up is which way assignability flows between T[] and readonly T[] — and why that direction is the safe one.

part ofTypeScriptoverview, primer and where to startread it →
on this pageshow

questions

5

In TypeScript, is `string[]` a different type from `Array<string>`, and which of the two forms can you write `readonly` in front of?

level: juniorimportance: must knowfreq 72%

answer

  1. one type, two spellings
  2. brackets are sugar for the generic
  3. the modifier keyword needs somewhere to attach
  4. readonly only on the bracket form
  5. ReadonlyArray<T> is the generic spelling

basics

~20 s

string[] and Array<string> are the same type — the bracket form is shorthand for the generic one. They differ only in syntax: readonly attaches to the bracket form (readonly string[]); the generic spelling is ReadonlyArray<string>.

solid answer

~40 s

They are the same type. `string[]` is sugar for `Array<string>`, the checker treats them as one type, and inference never distinguishes them, so you can assign either to the other freely. Choosing between them is style: the bracket form is terser for simple element types, while the generic form reads better for a long or union element type, since `Array<string | number>` needs no parentheses where `(string | number)[]` does. The one place they stop being interchangeable is the `readonly` modifier: `readonly string[]` is legal, but `readonly Array<string>` is a syntax error, because that modifier is only permitted on array and tuple literal type syntax — the generic spelling of that type is `ReadonlyArray<string>`. Nesting works in both forms: `string[][]` is `Array<Array<string>>`. Most teams pick one form and enforce it with a lint rule.

go deeper

for a junior

Be ready to say plainly that string[] and Array<string> are the same type and that the bracket form is shorthand. Knowing that readonly attaches only to the bracket form is the detail that lifts the answer above a shrug.

for a middle

Explain the mechanics: the bracket syntax expands to the Array interface, so inference, error text and assignability are identical, and the readonly modifier is a syntax restriction rather than a semantic difference between the forms.

for a senior

Show that you treat this as a codebase-consistency decision — one form chosen and lint-enforced — and point out the concrete consequence that a team on Array<T> must still reach for ReadonlyArray<T> when it wants immutable parameters.

for a principal

Frame it as a convention you standardise once and never relitigate, and be able to say why: the readability of long element types versus the readonly ergonomics, with the migration cost of flipping an established codebase weighed against zero semantic gain.

## Two spellings, one type TypeScript offers two syntaxes for "array of T". The bracket form writes the element type followed by `[]`, and the generic form instantiates the built-in `Array` interface: ```typescript const a: string[] = ['x']; const b: Array<string> = a; // same type, assignment is fine const c: string[] = b; // also fine ``` These are not two similar types that happen to be compatible — they are one type with two spellings. The bracket syntax is expanded by the compiler into `Array<T>`, so error messages, `keyof`, method lookup and inference all behave identically. Nothing about the choice reaches the emitted JavaScript either: types are erased at compile time, so both forms produce the same output. ## Nesting and element-type readability Both forms nest. `string[][]` is an array of arrays of strings, and its generic spelling is `Array<Array<string>>`. As the element type grows, the bracket form starts to strain: ```typescript type UserList = { id: string; name: string }[]; // legal, but the [] is easy to miss type UserList2 = Array<{ id: string; name: string }>; // the element type is bracketed for you ``` The practical argument for the generic form is that it puts the element type inside visible delimiters. The practical argument for the bracket form is brevity for short element types — `number[]` beats `Array<number>` for scanning. Neither is more correct; what matters is consistency, which teams usually settle with a lint rule rather than a per-file decision. ## Union element types force a decision The bracket form binds more loosely than a union, so an element union must be parenthesised: ```typescript type Mixed = (string | number)[]; // array whose elements are string or number type Mixed2 = Array<string | number>; // identical, no parentheses required ``` Without the parentheses you get a different type entirely, which is the single most common beginner mistake with the bracket syntax. ## Where the forms genuinely diverge: readonly The `readonly` type modifier is only permitted on array and tuple literal type syntax. So: ```typescript type Ok = readonly string[]; type Ok2 = ReadonlyArray<string>; // the generic spelling of the same type // type Bad = readonly Array<string>; // syntax error: readonly is not allowed here ``` `readonly string[]` and `ReadonlyArray<string>` denote the same type — an array-like type that carries the reading methods (`map`, `filter`, `slice`, `concat`, `indexOf`, `at`, …) but not the mutating ones (`push`, `pop`, `splice`, `sort`, `reverse`, `fill`), and whose index signature is readonly. If you have standardised on `Array<T>` everywhere, you still have to reach for `ReadonlyArray<T>` when you want immutability, because the modifier keyword has no place to attach in the generic form. Teams that standardise on the bracket form get `readonly T[]` for free, which is one honest reason to prefer it. When the element type is itself a union, both parts stack: `readonly (string | number)[]`, or `ReadonlyArray<string | number>`. ## What is not different A few things people expect to differ and do not: - **Inference.** An array literal is inferred as `T[]` in the bracket spelling in tooling output, but that is a display choice; the type is the same one `Array<T>` names. - **Mutability.** Neither form is more mutable than the other. `Array<string>` is not somehow "the mutable one" — `string[]` is equally mutable. - **Runtime.** Both erase completely. Neither adds a check, a wrapper, or any cost. - **Assignability.** Because they are the same type, there is no conversion, no widening, and no structural comparison to reason about between them. The takeaway an interviewer wants: you know these are one type, you can articulate the readonly asymmetry as the only hard difference, and you treat the rest as a style question your team settles once.

  • If the two forms are identical, is there any case where you cannot use the bracket form?
    Not for expressing the type itself — anything `Array<T>` can express, `T[]` can, given parentheses for unions and intersections. The pressure runs the other way: `readonly` attaches only to the bracket form, so a codebase standardised on `Array<T>` has to switch to `ReadonlyArray<T>` for immutable arrays. Beyond that it is legibility, not capability.
  • Does the choice of form change what TypeScript emits?
    No. Type annotations are erased entirely during compilation, so both forms emit the same JavaScript — in fact no JavaScript at all for the annotation itself. The distinction lives only in the checker, which is why the decision is purely about how the source reads to a human.
  • How would you write an array of arrays of numbers in both forms?
    `number[][]` in the bracket form and `Array<Array<number>>` in the generic form. Note that `readonly number[][]` makes only the outer array immutable — the inner arrays are still fully mutable. Making both levels readonly requires `readonly (readonly number[])[]`, which is where the bracket form starts to lose its readability advantage.

saying these in an interview costs you the question

  • Claims Array<T> is mutable and T[] is readonly
  • Says the two forms produce different JavaScript
  • Writes readonly Array<string> and expects it to compile
  • Thinks one form infers differently from the other
  • Believes Array<T> adds a runtime wrapper or cost

context

open as a page

In TypeScript, what types are inferred for `const mixed = [1, 'a']` and `const tags = ['a', 'b']`, and why is `tags` not inferred as `('a' | 'b')[]`?

level: middleimportance: must knowfreq 62%

basics

~20 s

mixed is inferred as (string | number)[] and tags as string[]. TypeScript takes the union of the element types and widens literal types, because a mutable array's slots can later be reassigned to any other string.

open as a page

In TypeScript, a `number[]` can be assigned to a variable of type `readonly number[]`, but not the other way round. Why does assignability only flow in that direction?

level: middleimportance: must knowfreq 58%

basics

~20 s

readonly number[] has the reading methods but not the mutating ones, so every number[] already satisfies it. The reverse assignment would hand push and sort to a value whose owner is relying on it not being mutated, so the compiler rejects it.

open as a page

In TypeScript, what do the annotations `string | number[]` and `(string | number)[]` each describe, and how would you write the second one using the `Array<...>` form?

level: juniorimportance: should knowfreq 48%

basics

~20 s

string | number[] is a union of a string and an array of numbers, because [] binds tighter than |. (string | number)[] is an array whose elements may each be a string or a number, written generically as Array<string | number>.

open as a page

You change a function's parameter annotation in TypeScript from `items: Item[]` to `items: readonly Item[]`. What does that annotation actually guarantee, and what does it not?

level: seniorimportance: should knowfreq 42%

basics

~20 s

It guarantees only that the function body cannot call mutating array methods or assign to indices, and only while type checking. It is shallow, so element objects stay mutable; it is erased at runtime; and the caller still holds a mutable reference to the same array.

open as a page