Types and inference

Type arrays

Describe a collection whose elements share a type and understand the equivalent bracket and generic syntax.

An array type answers one question: what can each element be? TypeScript gives you two ways to write the answer, and they mean exactly the same thing:

const names: string[] = ['Ada', 'Lin']
const moreNames: Array<string> = ['Grace']

string[] is the bracket form. Array<string> is the generic form. I use the bracket form almost everywhere, because it is shorter. The generic form reads better when the element type gets complex, like Array<string | number>, where the brackets would be easy to misplace.

You rarely need to write either one. Initialize an array with values and TypeScript infers the element type. const names = ['Ada', 'Lin'] is already a string[].

The element type guards every operation

Once TypeScript knows the element type, it checks everything you add later:

names.push('Margaret')
names.push(42)

The first call works. The second fails:

error TS2345: Argument of type 'number' is not assignable to parameter of type 'string'.

Reads are protected too. Every element you pull out of names is a string, so calling names[0].toUpperCase() is always safe as far as the type is concerned.

Inference flows through array methods

This is where arrays get pleasant. Array methods receive the element type through inference:

const uppercaseNames = names.map(name => name.toUpperCase())

You did not annotate name, but TypeScript knows it is a string, because names is a string[]. It also infers the result: toUpperCase() returns a string, so uppercaseNames is a string[]. Chain a .filter() or another .map() after it and the types keep flowing through.

Mixed elements need a union

Sometimes an array really does hold two kinds of values. Say so with a union:

const ids: Array<string | number> = ['a1', 42]

Now each element is string | number. When you read one, you have to check which it is before calling a string method or doing arithmetic. That extra step is the honest price of a mixed collection. Only pay it when the mix is intentional.

Do not widen to any[]

Here is the mistake to avoid. When a push fails, do not “fix” it by changing the type to any[]. That removes checking from every read afterwards. A typo like names[0].toUppercase() would compile fine and crash at runtime.

If TypeScript rejects a push, the element type is telling you something about your data. Either the value is wrong, or the array really needs a wider union. Decide which, and write it down in the type.

Try this on your own: create a number[], map it to formatted strings with toFixed(2), and hover over the result to confirm TypeScript inferred string[].

Lesson completed