The Type-System Mindset
A type is a set of values
Section titled “A type is a set of values”Here is the reframe that unlocks TypeScript. Stop thinking of a type as a label and start thinking of it as a set — the collection of every value that legally belongs to it.
booleanis the set{ true, false }— exactly two members.undefinedis a set with one member:{ undefined }.stringis the (infinite) set of all strings.42— yes, a literal — is a set with one member:{ 42 }. This is a literal type.
Once types are sets, the operations you already know from math explain the whole system.
Assignability is the subset relationship
Section titled “Assignability is the subset relationship”a is assignable to b if and only if the set a is a subset of the set b. That’s the entire rule.
let x: 42 = 42;let y: number = x; // ✅ {42} ⊆ all numbers
let a: number = 42;let b: 42 = a; // ❌ all numbers ⊄ {42}// Error: Type 'number' is not assignable to type '42'.The literal 42 fits into number because {42} is a subset of all numbers. The reverse fails because most numbers aren’t 42. You never have to memorize assignability rules — you just ask “is this set inside that set?”
Unions are set union; intersections are set intersection
Section titled “Unions are set union; intersections are set intersection”flowchart TB
subgraph u["A | B (union = OR)"]
ua["string"] --- ub["number"]
note1["a value in EITHER set
→ more values, wider"]
end
subgraph i["A & B (intersection = AND)"]
note2["a value in BOTH sets
→ fewer values, narrower"]
end string | numberis the union of the two sets — a value that’s in either. Unions are wider (more members).A & Bis the intersection — a value that’s in both. For object types this means “has all properties of both,” which is why intersections narrow.
A common surprise falls right out of this: string & number is the empty set. No value is both a string and a number — so its type is never.
The two ends: never and unknown
Section titled “The two ends: never and unknown”Sets have a smallest and largest, and TypeScript names them:
neveris the empty set{ }— no values at all. Nothing is assignable tonever(you can’t produce a member of an empty set), butneveris assignable to everything (the empty set is a subset of every set). It’s what a function that never returns is typed as, and the signal that a branch is unreachable.unknownis the set of all values — the top type. Everything is assignable tounknown, butunknownis assignable to almost nothing until you narrow it. It’s the type-safe version ofany.
let n: never;let u: unknown = "anything goes";
let s: string = u; // ❌ unknown is too wide — narrow it firstlet x: unknown = 42; // ✅ everything fits into unknownany breaks this model on purpose: it opts out of set-checking entirely, which is why it’s dangerous. unknown keeps the model and forces you to narrow.