Overview
TypeScript is a structural type system bolted onto JavaScript with zero runtime presence — types are erased at compile time, which defines both its power (gradual adoption, models any JS pattern) and its limits (no runtime guarantees; external data needs validation). Going deep means moving from "annotating variables" to using the type system as a design tool: encoding domain rules so that invalid states fail to compile.
Key points
- Structural, not nominal: compatibility is by shape — any object with the right
members satisfies an interface. Powerful and occasionally surprising (two unrelated
types unify); branded types (
string & { __brand: 'UserId' }) opt back into nominal-style distinctions where identity matters. - Unions + narrowing are the heart: model alternatives as unions, and let control
flow narrow them —
typeof/inguards, discriminated unions (a literalkindfield) with exhaustiveswitch+neverchecks. This is the "make invalid states unrepresentable" pattern:{status:'loading'} | {status:'ok', data} | {status:'error', error}instead of nullable fields that lie. - The strictness dials decide what the types are worth:
strict: trueas the floor,noUncheckedIndexedAccessfor honest arrays/records;anysilently disables checking transitively —unknownis the honest "don't know yet" that forces narrowing. - The type-level toolkit: generics with constraints,
keyof/indexed access, mapped and conditional types,infer, template-literal types — the mechanics behind utility types (Pick,Omit,ReturnType). Use to derive types from single sources of truth (as const+typeof) rather than duplicating shapes; resist type-level cleverness that reads like a puzzle. - Types end at runtime boundaries: API responses, env vars, and file contents are
unknownwearing a costume — validate at the edge (Zod-style schema → inferred static type: one definition, both worlds) instead ofas-casting hope into the codebase. - Ecosystem mechanics:
tsctype-checks while faster transpilers do the emitting; declaration files (.d.ts) carry types for JS libraries; ESM/CJS module settings remain the sharpest operational edge. - To explore: variance (why function parameters are special + method bivariance),
satisfiesvs. annotation vs. assertion, monorepo project references, the type-system's Turing-completeness (as a warning, mostly).
Practice
- Exercism TypeScript track (source) — small mentored exercises to make narrowing, unions, and generics reflexive.
- type-challenges (source) —
the type-level toolkit as a kata ladder: mapped/conditional types,
infer, template literals — implementPickbefore you reach for it. - Validate a real edge with Zod (source) — take an API your code
consumes, write the schema, infer the static type from it, and delete every
ascast on that path; one definition, both worlds, in practice. - Coding Challenges in TypeScript (source) —
build a JSON parser or
wcclone with a discriminated-union AST and exhaustiveswitches; "invalid states unrepresentable" on a real program.
Related
- Browser internals — the runtime the erased types leave behind.
- Clean code — types as the strongest form of intention-revealing naming.
- DDD — value objects and unions are its tactical patterns in type form.
- Covariance & contravariance — the variance thread flagged above, explored: function subtyping and method bivariance.
- API design — schema-first contracts generate these types.
- Component architecture & state management — making invalid states unrepresentable is state management's other half.
- Hexagonal reference implementation — Frontend —
project references,
exportsmaps and thelibdial put to work as architectural walls. - The TypeScript build ecosystem — the toolchain around the erased types: the check/emit split, package managers, bundlers, and the ESM/CJS edge in full.