Software / Browser

The type system

Types are a lattice. unknown, never, and any sit in different places, and the advanced patterns are how you move around that lattice.

$[ts_type_system]
structural typing type lattice inference narrowing

TypeScript Type System

TypeScript uses structural (duck) typing — compatibility is determined by shape, not identity. The type system forms a lattice: unknown is the top type (all values are assignable to it); never is the bottom type (no value inhabits it). any escapes the lattice entirely.

TypeScript Type Lattice Type hierarchy: unknown at top (all types assignable to it). Below: primitive types (string, number, boolean, symbol, bigint, object). Below primitives: literal types (string literals, numeric literals, true/false). never at bottom (assignable to everything, inhabited by nothing). any shown separately as a bypass that escapes the lattice bidirectionally. Arrows point from supertype to subtype (assignment direction). unknown top type — require narrowing before use string number boolean bigint object symbol undef | null "hello"… 0|1|42… true|false ← literal types are subtypes of their primitive never bottom type — assignable to everything, inhabited by nothing any ⚠ lattice bypass
unknown vs any

unknown — safe top type

unknown requires narrowing before use (type-safe). any escapes the type system entirely — bidirectional assignability. Prefer unknown for external data (JSON, API responses, user input). noImplicitAny bans accidental widening to any.

  • unknown
  • any
  • noImplicitAny
  • type assertion
never

never — bottom type

Inhabited by nothing. Appears at exhaustive checks (all union members handled), in return types of functions that always throw, and as the intersection of contradictory types (string & number). never is assignable to every type — use it to detect missing branches.

  • exhaustive check
  • assertion function
  • unreachable code
structural

Structural Typing

Compatibility is shape-based: T is assignable to U if T has at least all required properties of U with compatible types. Excess property checking applies only at fresh object literal assignment, not at variable assignment — a common source of confusion.

  • excess property check
  • freshness
  • index signatures
  • covariance
  • contravariance
is / asserts

Type Narrowing

Built-in: typeof, instanceof, in, truthiness, equality. User-defined: x is T type predicate returns a boolean; asserts x is T asserts or throws. Discriminated unions: a shared literal property identifies each variant — TS narrows based on the literal.

  • type predicate
  • discriminated union
  • control flow analysis
  • asserts
Utility<T>

Utility Types

Standard type transforms: Partial, Required, Readonly, Pick<T,K>, Omit<T,K>, Record<K,V>, Exclude, Extract, NonNullable, ReturnType<F>, InstanceType<C>, Awaited<T>, NoInfer<T>.

  • Partial
  • Omit
  • ReturnType
  • Awaited
  • NoInfer
satisfies

satisfies + as const

satisfies T checks a value against a type without widening the inferred type. as const produces readonly literal types. Combined: const p = {...} as const satisfies Record<...> — constraint checking with precise inference preserved. Available since TypeScript 4.9.

  • satisfies
  • as const
  • const type parameters
  • const assertions
^[ts_advanced]
generics conditional types mapped types template literals declaration merging

TypeScript Advanced Patterns

T extends U

Generics + Inference

Constraints: T extends K restricts T to subtypes of K. Inference sites: TS infers from call sites, conditional clauses, infer. NoInfer<T> blocks inference at a site without removing the constraint. Variance: function params are contravariant; return types covariant.

  • constraints
  • infer
  • NoInfer
  • variance
  • covariance
  • const type param
T extends U ? X : Y

Conditional Types

infer R captures a type within a conditional branch. Distributivity: over a naked union type parameter, the conditional distributes member-by-member. To prevent distribution, wrap in a tuple: [T] extends [U]. Basis for ReturnType, Awaited, Parameters.

  • infer
  • distributive
  • [T] extends [U]
  • deferred
{ [K in T]: … }

Mapped Types

Iterate over a union of keys. Modifiers: +readonly, -readonly, +?, -?. Key remapping with as: [K in keyof T as Capitalize<string & K>]. Filter keys by returning never from the as clause. Homomorphic mapped types preserve optionality and readonly.

  • keyof
  • in keyof
  • as clause
  • modifiers
  • homomorphic
`${A}${B}`

Template Literal Types

Construct string types from literal unions. Combine with mapped types to derive event names, accessor keys, CSS property strings. Built-in string intrinsics: Uppercase, Lowercase, Capitalize, Uncapitalize. infer inside template literals matches string patterns.

  • Capitalize
  • template literal infer
  • string union
  • intrinsic string types
declare module

Declaration Merging

Interfaces merge across declarations; type aliases do not. Module augmentation: declare module 'x' { ... } extends an existing module's types. Global augmentation: declare global { ... }. Commonly used for Express Request, Jest matchers, Vite ImportMeta, and environment variables.

  • interface merging
  • module augmentation
  • ambient declaration
  • declare global
tsconfig

tsconfig + Project References

Key flags: strict enables noImplicitAny + strictNullChecks + more. moduleResolution: "bundler" for Vite/esbuild. verbatimModuleSyntax enforces explicit type-only imports. Project references (composite: true + references) enable incremental builds and type-check boundaries across monorepo packages.

  • strict
  • composite
  • references
  • verbatimModuleSyntax
  • isolatedModules