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?
answer
- postfix binds tighter than the bar
- one of them is not an array at all
- parentheses change what [] attaches to
- Array<...> spells the grouping for you
basics
~20 sstring | 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>.
solid answer
~50 sThe `[]` suffix binds more tightly than the union bar, so `string | number[]` parses as `string | (number[])` — a value that is either a string or an array of numbers, never an array of mixed elements. Parenthesising changes the grouping: `(string | number)[]` is a single array type whose element type is the union, so `['a', 1]` is valid. The generic form makes the grouping explicit and needs no parentheses at all — `Array<string | number>` is exactly the same type. A third, genuinely different type is worth knowing here: `string[] | number[]` is an array that is either all strings or all numbers, so it rejects a mixed literal. Getting an operator-precedence typo like this into a signature is one of the more common ways an annotation silently means something you did not intend.
code
typescript · 13 lineslet a: string | number[];
a = 'hello';
a = [1, 2];
// a = ['x', 1]; // Error: not assignable to 'string | number[]'
const b: (string | number)[] = ['x', 1];
const c: Array<string | number> = b; // identical type, no parentheses needed
let d: string[] | number[] = ['x', 'y'];
d = [1, 2];
// d = ['x', 1]; // Error: a mixed literal matches neither branch
console.log(a, b, c.length, d.length);go deeper
Be ready to say that [] attaches to the type immediately before it, so the parentheses decide whether you have an array or a union. Reading an annotation out loud carefully is most of the answer here.
Explain the precedence rule rather than reciting the two cases, and show the same trap on a function element type. Be able to name Array<string | number> as the unambiguous spelling of the parenthesised form.
Turn it into a modelling point: choose between an element union and a union of homogeneous arrays based on whether mixing is a real possibility or an invariant violation, and say what each choice costs the code that consumes the value.
Own the review angle — precedence typos compile and surface as errors at distant call sites, so argue for conventions that make the grouping visually explicit in shared API signatures rather than relying on every reader to parse correctly.
## Precedence, not semantics The confusion here is purely syntactic. In TypeScript's type grammar, the array suffix `[]` is a postfix operator that binds more tightly than the union operator `|`. So: ```typescript type A = string | number[]; // parses as: string | (number[]) type B = (string | number)[]; // parses as: (string | number)[] ``` `A` is a union of two types. A value of type `A` is either a string, or an array whose elements are all numbers. It is never an array containing both. `B` is one type: an array, whose element type happens to be a union, so any individual slot may hold a string or a number. ```typescript let a: string | number[]; a = 'hello'; // ok a = [1, 2]; // ok // a = ['x', 1]; // error: not assignable to string | number[] let b: (string | number)[] = ['x', 1]; // ok ``` ## The generic form removes the ambiguity Because `Array<T>` puts the element type inside angle brackets, there is nothing to mis-group: ```typescript type B2 = Array<string | number>; // identical to (string | number)[] ``` This is the strongest concrete argument for the generic spelling: with a union, an intersection, or a function element type, the bracket form needs parentheses you can forget, and forgetting them produces a type that still compiles and still looks plausible. `() => void[]` versus `(() => void)[]` is the same trap with a function element type — the first is a function returning an array of `void`, the second an array of functions. ## A third type people conflate with both ```typescript type C = string[] | number[]; ``` `C` is a union of two array types: a homogeneous array of strings, or a homogeneous array of numbers. It is not the same as `(string | number)[]`. A mixed literal like `['x', 1]` is assignable to `(string | number)[]` but not to `string[] | number[]`, since it matches neither branch. And a `string[]` is assignable to both `C` and `(string | number)[]`, so the difference only shows up when you try to mix. Which you want is a modelling decision. If your data genuinely cannot mix — a column of values that is either all numeric or all textual — `string[] | number[]` encodes that invariant and `(string | number)[]` throws it away. If elements are independently typed, the element union is the right shape. ```typescript function render(values: (string | number)[]) { /* per-element handling */ } function sum(values: number[] | string[]) { /* two whole-array cases */ } ``` ## Stacking readonly on top The readonly modifier sits outside the whole array literal syntax, so it composes with the parentheses rather than replacing them: ```typescript type D = readonly (string | number)[]; type D2 = ReadonlyArray<string | number>; // same type ``` Writing `readonly string | number[]` would be a syntax error, because the modifier has to attach to an array or tuple literal type, not to a union. ## Reading an annotation you did not write A practical habit: when you meet `X | Y[]` in someone else's signature, read the `[]` as attaching to `Y` alone until you see parentheses proving otherwise. That single reflex catches most of these. If the surrounding code then treats the parameter as "an array of X-or-Y", the annotation is wrong and the compiler has been quietly agreeing with the typo — it will reject the mixed call sites rather than the declaration, so the error appears far from the mistake. The interviewer's real target with this question is whether you read type syntax precisely rather than by shape. The answer is short; the follow-up is usually to hand you a signature and ask what it accepts.
- How would you write an array of functions that take no arguments and return void?`(() => void)[]`, or `Array<() => void>`. Without the parentheses, `() => void[]` is a single function type that returns an array of `void` — the same precedence trap as the union case, since the array suffix attaches to the return type rather than to the whole function type. The generic form avoids the ambiguity entirely.
- When would you deliberately choose `string[] | number[]` over `(string | number)[]`?When the array is homogeneous by invariant — every element is the same kind, and mixing would be a bug. The union-of-arrays encodes that rule in the type, so a mixed literal is rejected at the declaration. The element union is right when slots are independently typed, such as a row of display values.
saying these in an interview costs you the question
- Reads string | number[] as an array of mixed values
- Thinks the parentheses are optional styling
- Says (string | number)[] and string[] | number[] are the same
- Assumes Array<string | number> needs parentheses too
- Writes readonly string | number[] expecting a readonly array