skip to content

Arrays & Tuples

Typing ordered collections: homogeneous arrays whose length is unknown, and tuples whose positions each carry their own type and meaning. Interviewers probe the readonly variants and tuple rest/label syntax because they show up all over modern generic and React code.

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

explore

questions

10

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 does the annotation `[string, number]` guarantee about a value that the annotation `(string | number)[]` does not?

level: juniorimportance: must knowfreq 68%

basics

~20 s

A tuple type fixes both the length and the type of each position: [string, number] accepts exactly two elements, a string then a number. (string | number)[] accepts any number of elements, in any order.

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

In the TypeScript tuple type `[string, number?, ...boolean[]]`, what do the `?` and the `...` mean, and what is that tuple's `length` type?

level: middleimportance: should knowfreq 42%

basics

~20 s

The ? marks an optional element that may be omitted, and reading it yields number | undefined. The ... is a rest element allowing zero or more booleans at the end. Because the rest element makes the size open-ended, length is typed number.

open as a page

In TypeScript, a function `move(x: number, y: number)` is called as `move(...args)`. Why does that fail to compile when `args` is `number[]`, and what type of `args` makes it work?

level: middleimportance: should knowfreq 40%

basics

~20 s

A number[] has unknown length, so the compiler cannot prove the call supplies exactly two arguments and reports that a spread argument must have a tuple type or be passed to a rest parameter. Typing args as the tuple [number, number] fixes the arity and the call compiles.

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

Given `const point: [number, number] = [1, 2]` in TypeScript, why does `point.push(3)` compile even though the type says the tuple holds exactly two elements, and what annotation prevents it?

level: seniorimportance: should knowfreq 38%

basics

~20 s

A tuple is an ordinary array at runtime, so it inherits Array's methods, and push is typed to accept the tuple's element types. The length guarantee covers assignment and indexing, not mutation. Annotating the value as readonly [number, number] removes push and the other mutators.

open as a page

In TypeScript, what do the labels in the tuple type `[first: string, second?: number]` change, and what rule does the compiler enforce about labelling?

level: middleimportance: nice to knowfreq 24%

basics

~20 s

Labels are documentation for humans and tooling: they show in editor hints and become the parameter names when the tuple is used as a rest parameter list. They do not affect type identity or assignability. The rule is all-or-nothing — every element must be labelled, or none.

open as a page