Type-Safe Patterns in Practice
Use the type system to make bugs impossible at the boundaries and in your state, and know exactly where TypeScript trusts you instead of checking.
Key points
- 1
Treat external data (fetch, JSON.parse, env vars, webhooks) as
unknown, validate it once with a schema, and derive the static type from the schema (for examplez.infer). - 2
Model state as discriminated unions and close them with a
neverexhaustiveness check or an explicit return type, so new variants fail to compile where they are unhandled. - 3
Branded types (preferably keyed by a
unique symbol) separate structurally identical IDs; create them only in one validating constructor. - 4
Prefer
satisfiesover annotations for config tables, and addas constwhen you need literal values; derive unions from const arrays withtypeof arr[number]. - 5
Type higher-order functions with constrained generics and
Parameters<F>, but rememberinferpicks the last overload of an overloaded function. - 6
Write dedicated input types (
Partial<Omit<User, "id">>) instead of reusing entity types, and considerexactOptionalPropertyTypesandnoUncheckedIndexedAccessfor extra safety. - 7
Migrate JavaScript gradually:
allowJs, thencheckJs, file-by-file renames from the leaves, then ratchet strictness up flag by flag.
Common traps
as,as unknown as,!and user-defined type predicates are unchecked claims; a return-only generic likegetJson<T>()is an assertion in disguise.Promise
.catch((e) => ...)callbacks gete: anyeven withuseUnknownInCatchVariables; onlycatch (e)clauses becomeunknown.An unused (phantom) type parameter has no effect on assignability, so
Builder<false>andBuilder<true>are the same type until a member mentions it.
Read the source
Test yourself on Type-Safe Patterns in Practice
Ten questions, with the answer and explanation after each one.