Handbooks / TypeScript / Chapter 1
Basic Types
7 pages · ~5 min✓ Reviewed
Start here. Next up: Interfaces, Types and Unions.
Annotations and Inference Mental Model
1. Annotation vs Inference
- Annotation = explicit type after a colon:
let n: number = 5 - Types are erased at compile time: no runtime effect
- Inference: TS derives the type from the initializer
| Code | Type |
|---|---|
let n: number = 5 | number (explicit) |
let n = 5 | number |
const n = 5 | 5 (literal type) |
2. Where to Annotate
- Annotate parameters: never inferred from the body
- Annotate exported return types
- Let infer locals and callbacks
- Contextual typing: the array type gives
xits type
typescript
export function double(xs: number[]): number[] { return xs.map(x => x * 2); // x: number }
no annotation needed on x
3. Common Mistake
typescript
let x; // implicit any (evolving any) x = 5; // now treated as number let y: number; // fix: annotate
or initialize: let x = 5
No initializer, no type
let x; is implicitly any. Annotate it or initialize it.
Primitives, Arrays and Tuples
1. Primitives
- Lowercase only:
stringnumberbooleanbigintsymbolnullundefined - Never
String/Number: those are wrapper objects, not the primitives
2. Arrays and Tuples
| Type | Example | Rule |
|---|---|---|
number[] | Array<number> | same type, two spellings |
readonly number[] | const r: readonly number[] | blocks push and r[0] = 2 |
[string, number] | let p: [string, number] = ['a', 1] | fixed length, type per position |
[string, number?] | ['a'] | optional last element |
[name: string, age: number] | labelled tuple | labels are documentation only |
3. Inference
- An array literal infers an array, not a tuple
as constgives a readonly tuple of literal types
typescript
const a = ['x', 1] // (string | number)[] const b = ['x', 1] as const // readonly ['x', 1]
Mistake
push on a non-readonly tuple compiles and breaks its length guarantee. Use a readonly tuple.
any, unknown and never
1. any vs unknown
anydisables checking: every operation compiles, errors surface at runtimeanyspreads through assignmentsunknown= safe top type: anything goes in- Narrow first:
typeof,instanceof, type guard
| any | unknown | |
|---|---|---|
| Assign to it | anything | anything |
| Assign from it | to everything | only unknown, any |
| Use directly | allowed | error |
ts
let a: any = 'x'; a.foo(); // compiles, throws let u: unknown = JSON.parse(s); if (typeof u === 'string') u.length;
2. never and exhaustiveness
never= empty bottom type- Return type of functions that always throw or loop
- Type left after exhaustive narrowing
- Assign the
defaultleftover toconst _x: never = v - New union member becomes a compile error
ts
switch (s.kind) {
case 'circle': return 1;
case 'square': return 2;
default:
const _x: never = s;
return _x;
}Rule
Untrusted input (JSON.parse, catch variables) is unknown. Avoid any.
void and Enums vs Union Literals
1. void, undefined, never
| Type | Means |
|---|---|
void | result ignored |
undefined | result is the value undefined |
never | never returns at all |
- Callback typed
() => voidmay return a value, ignored - Function declared
: voidcannot return one - Since TS 5.1,
voidandundefinedneed no explicitreturn
2. Enums emit JS
- Numeric enum is reverse-mapped:
E[0]gives the name - Older TS: numeric enum accepts any number
- String enum: not reverse-mapped, safer
enumemits a runtime objectconst enumis inlined, emits nothing, breaks with isolatedModules
typescript
enum E { A, B }
E[0] // "A"3. Prefer union literals
type Dir = 'up' | 'down': no JS, structural, tree-shakeable- Need runtime values?
as constobject pluskeyof typeof Obj
typescript
const Dir = { Up: 'up', Down: 'down' } as const; type Dir = (typeof Dir)[keyof typeof Dir];
Default
Union literals over enums.
Strict Mode and Common Mistakes
1. What strict turns on
"strict": truein tsconfig enables the whole family- noImplicitAny, strictNullChecks, strictFunctionTypes, strictBindCallApply, strictPropertyInitialization
- noImplicitThis, alwaysStrict, useUnknownInCatchVariables, strictBuiltinIteratorReturn (TS 5.6+)
2. The big three
| Flag | Error when | Fix |
|---|---|---|
| strictNullChecks | null or undefined used as another type | check or narrow first |
| noImplicitAny | parameter has nothing to infer from | annotate it |
| strictPropertyInitialization | field not set in the constructor | assign, ! or ? |
string | nullforces a check before use- Field
!= definite assignment,?= optional
typescript
function len(s: string | null) { return s.length; // error: possibly null } class User { id!: number; // assigned elsewhere nick?: string; // may stay undefined }
3. Common mistakes
- Overusing
ascasts to silence errors: unchecked, only asserts - Overusing non-null
x!: unchecked, crashes at runtime if null noUncheckedIndexedAccessis not in strict: enable it separately
Remember
as and ! assert, they never check. With noUncheckedIndexedAccess, arr[0] becomes T | undefined.
TypeScript Types Cheat Sheet
1. Annotate and Collect
- Annotate params and exported returns:
function f(a: A): R let x: T; let locals and callbacks inferas constkeeps literals:readonly ['x', 1]
| Type | Meaning |
|---|---|
T[] | array |
readonly T[] | no push, no index assign |
[A, B] | tuple: fixed length |
[a: A, b?: B] | labelled, optional slot |
2. Escape Hatches and Special Types
| Rank | Tool | Checked? |
|---|---|---|
| 1 | unknown | safe: narrow first |
| 2 | any | unsafe: checks off |
| 3 | as | assertion, unchecked |
| 4 | ! | non-null assertion, unchecked |
void: return value ignorednever: unreachable or exhausted union
typescript
default:
const _x: never = v;a new union member becomes a compile error
3. Choose Safely
- Prefer union literals:
type Dir = 'up' | 'down'; no JS emitted strict: truealso covers strictBuiltinIteratorReturn (5.6+)- Add
noUncheckedIndexedAccessyourself: not in strict
Remember
as and ! only assert; they silence the compiler, they never check.
Check yourself
With strict on, does let x; x = 5; x = 'hi'; compile?
- Yes:
let x;is an evolving any, so each assignment is accepted. - Fix: write
let x: number = 5;or initialize it.
What is wrong with function len(v: unknown) { return v.length; }?
- Error: an
unknownvalue cannot be used until it is narrowed. - Fix:
if (typeof v === 'string') return v.length;
A switch on type Dir = 'up' | 'down' ends with default: const _x: never = d;. You add 'left' to Dir. What happens?
- Compile error in
default:'left'is not assignable tonever. - That error is the point: it finds the unhandled case.