Handbooks / TypeScript / Chapter 1

Basic Types

7 pages · ~5 min✓ Reviewed

Start here. Next up: Interfaces, Types and Unions.

TypeScriptPage 1

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
CodeType
let n: number = 5number (explicit)
let n = 5number
const n = 55 (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 x its 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.

TypeScriptPage 2

Primitives, Arrays and Tuples

1. Primitives

  • Lowercase only: string number boolean bigint symbol null undefined
  • Never String / Number: those are wrapper objects, not the primitives

2. Arrays and Tuples

TypeExampleRule
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 tuplelabels are documentation only

3. Inference

  • An array literal infers an array, not a tuple
  • as const gives 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.

TypeScriptPage 3

any, unknown and never

1. any vs unknown

  • any disables checking: every operation compiles, errors surface at runtime
  • any spreads through assignments
  • unknown = safe top type: anything goes in
  • Narrow first: typeof, instanceof, type guard
anyunknown
Assign to itanythinganything
Assign from itto everythingonly unknown, any
Use directlyallowederror
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 default leftover to const _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.

TypeScriptPage 4

void and Enums vs Union Literals

1. void, undefined, never

TypeMeans
voidresult ignored
undefinedresult is the value undefined
nevernever returns at all
  • Callback typed () => void may return a value, ignored
  • Function declared : void cannot return one
  • Since TS 5.1, void and undefined need no explicit return

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
  • enum emits a runtime object
  • const enum is 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 const object plus keyof typeof Obj
typescript
const Dir = { Up: 'up', Down: 'down' } as const;
type Dir = (typeof Dir)[keyof typeof Dir];
Default

Union literals over enums.

TypeScriptPage 5

Strict Mode and Common Mistakes

1. What strict turns on

  • "strict": true in tsconfig enables the whole family
  • noImplicitAny, strictNullChecks, strictFunctionTypes, strictBindCallApply, strictPropertyInitialization
  • noImplicitThis, alwaysStrict, useUnknownInCatchVariables, strictBuiltinIteratorReturn (TS 5.6+)

2. The big three

FlagError whenFix
strictNullChecksnull or undefined used as another typecheck or narrow first
noImplicitAnyparameter has nothing to infer fromannotate it
strictPropertyInitializationfield not set in the constructorassign, ! or ?
  • string | null forces 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 as casts to silence errors: unchecked, only asserts
  • Overusing non-null x!: unchecked, crashes at runtime if null
  • noUncheckedIndexedAccess is not in strict: enable it separately
Remember

as and ! assert, they never check. With noUncheckedIndexedAccess, arr[0] becomes T | undefined.

TypeScriptPage 6

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 infer
  • as const keeps literals: readonly ['x', 1]
TypeMeaning
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

RankToolChecked?
1unknownsafe: narrow first
2anyunsafe: checks off
3asassertion, unchecked
4!non-null assertion, unchecked
  • void: return value ignored
  • never: 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: true also covers strictBuiltinIteratorReturn (5.6+)
  • Add noUncheckedIndexedAccess yourself: not in strict
Remember

as and ! only assert; they silence the compiler, they never check.

TypeScriptPage 7

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 unknown value 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 to never.
  • That error is the point: it finds the unhandled case.