Study notes · 10% of the exam

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. 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 example z.infer).

  2. 2

    Model state as discriminated unions and close them with a never exhaustiveness check or an explicit return type, so new variants fail to compile where they are unhandled.

  3. 3

    Branded types (preferably keyed by a unique symbol) separate structurally identical IDs; create them only in one validating constructor.

  4. 4

    Prefer satisfies over annotations for config tables, and add as const when you need literal values; derive unions from const arrays with typeof arr[number].

  5. 5

    Type higher-order functions with constrained generics and Parameters<F>, but remember infer picks the last overload of an overloaded function.

  6. 6

    Write dedicated input types (Partial<Omit<User, "id">>) instead of reusing entity types, and consider exactOptionalPropertyTypes and noUncheckedIndexedAccess for extra safety.

  7. 7

    Migrate JavaScript gradually: allowJs, then checkJs, 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 like getJson<T>() is an assertion in disguise.

  • Promise .catch((e) => ...) callbacks get e: any even with useUnknownInCatchVariables; only catch (e) clauses become unknown.

  • An unused (phantom) type parameter has no effect on assignability, so Builder<false> and Builder<true> are the same type until a member mentions it.

Test yourself on Type-Safe Patterns in Practice

Ten questions, with the answer and explanation after each one.