Part 1 · 1 chapters · ~8 min

Type Systems

Static and dynamic checking, strong and weak typing, nominal and structural typing, type inference (local and Hindley-Milner), generics with erasure versus monomorphisation, variance (covariance, contravariance, invariance), algebraic data types and pattern matching, branded types, and soundness and its escape hatches (any, unsafe, casts).

2

Making illegal states unrepresentable

code
// TypeScript: a discriminated union, so a settled payout always has a reference
type Payout =
  | { state: 'pending'; id: string }
  | { state: 'settled'; id: string; railRef: string; settledAt: Date }
  | { state: 'failed'; id: string; reason: string };
function label(p: Payout) {
  switch (p.state) {
    case 'pending': return 'Processing';
    case 'settled': return `Sent (${p.railRef})`;        // railRef exists here, checked by the compiler
    case 'failed':  return `Failed: ${p.reason}`;
    default: { const never: never = p; return never; }   // adding a state without handling it is a compile error
  }
}
// branded ids: structural typing would otherwise accept any string
type AccountId = string & { readonly __brand: 'AccountId' };
type UserId = string & { readonly __brand: 'UserId' };
code
// Rust: the same idea with an enum and exhaustive match
enum Payout { Pending { id: String }, Settled { id: String, rail_ref: String }, Failed { id: String, reason: String } }
// Haskell infers types without any annotations (Hindley-Milner):  compose f g x = f (g x)
//   inferred: compose :: (b -> c) -> (a -> b) -> a -> c

Generics implementations: Rust and C++ monomorphise (a copy of the code per type: fast, larger binaries); Java erases (one copy, type information gone at runtime); Go uses a hybrid (GC shape stenciling with dictionaries).

FOUR AXES OF A TYPE SYSTEM
where each language sits
static vs dynamicTypes checked before running (Go,Rust, TS) or while running(Python, Ruby, JS).strong vs weakImplicit conversions: JS "1" + 1 ="11"; Python raises TypeError.nominal vs structuralCompatible by declared name (Java,Rust) or by shape (TypeScript, Gointerfaces).inferenceTypes worked out by the compiler:local (Go, Java var) to global(Haskell, OCaml).genericsCode over many types: List<T>,constraints, monomorphisation vserasure.varianceIs List<Cat> a List<Animal>? Safeonly for read-only use.
swipe the figure sideways, or tap expand for full screen
1/4
static vs dynamic
Static checking catches type errors before deployment and powers editor tooling; dynamic typing gives flexibility and faster prototyping. Gradual typing (TypeScript, Python type hints) mixes both.
before or during runninggradual typing mixes them