Everyday Types, Literals and Inference
Know how TypeScript infers and widens types, the difference between the top and bottom types, and when to reach for tuples, `as const`, `satisfies` or a union instead of an enum.
Key points
- 1
A
constholding a primitive literal gets the literal type ("hello"); aletand any object property widen tostring,numberand so on.as conststops widening, makes propertiesreadonlyand turns array literals into readonly tuples. - 2
anyandunknownboth accept every value, butunknownmust be narrowed before use.neverhas no values and is assignable to everything;voidmeans the return value should be ignored. - 3
{}accepts every value exceptnullandundefined(primitives included);objectaccepts only non-primitives. Optional properties read asT | undefined. - 4
Tuples fix length and per-position types: optional elements make
lengtha union such as1 | 2, and a rest element makes itnumber. Mutable tuples still allowpush, so preferreadonlytuples. - 5
Numeric enums emit an object with reverse mappings; string enums have none and are nominal. Since TS 5.0, out-of-range numeric literals are rejected. Unions of literals are erased entirely.
- 6
Annotations replace the inferred type,
satisfiesvalidates while keeping it, andasoverrides it (only between overlapping types).!removesnull | undefinedand emits no runtime check. - 7
typeof xin a type position is a type query.keyof typeof SomeEnumgives the member names, and${SomeEnum}gives the string values.
Common traps
const shape = { kind: 'circle' }giveskind: string, not'circle'. Theconstprotects the variable, not its properties.A function declaration that always throws is inferred as returning
void, notnever. Annotate: neverexplicitly when you want control-flow narrowing after the call.asand!only change what the compiler believes. A wrong assertion still crashes at runtime.
Read the source
Test yourself on Everyday Types, Literals and Inference
Ten questions, with the answer and explanation after each one.