TypeScript Advanced Features
TypeScript gets powerful when you treat types as a design tool, not just annotations. The advanced type system lets you model tricky real-world constraints — making impossible states unrepresentable — while keeping code ergonomic.
The skill is restraint as much as power: these features can build elegant, self-checking APIs, but over-engineered "type gymnastics" become unmaintainable. Reach for them to encode real invariants, and keep readability in mind.
TL;DR
- Use narrowing (and
never) to make impossible states unrepresentable. - Use conditional + mapped types to build reusable type utilities.
- Use
as constandsatisfiesto keep literals precise without losing ergonomics. - Prefer readability — a slightly less "clever" type usually scales better.
Quick Example
A never assignment turns a missing switch case into a compile error — exhaustiveness for free:
as const & satisfies
Conditional Types
Express "if this type, then that type" — and extract inner types with infer:
Mapped Types
Transform one type into another by iterating its keys:
Template Literal Types
Model string formats — IDs, keys, event names — at the type level:
Utility Types
Best Practices
- Encode real invariants (discriminated unions + exhaustiveness), not cleverness for its own sake.
- Reach for
satisfiesto keep literal precision while checking shape. - Prefer
unknown+ narrowing overanyfor untyped inputs. - Keep complex types named and documented — future readers (including you) will thank you.
Common Mistakes
Type gymnastics
any creeping in from untyped libraries
FAQ
How do I get exhaustiveness checking?
Use a discriminated union and assign the variable to never in the default case of a switch. When you add a new union member, TypeScript errors at that assignment because the new type is no longer assignable to never — forcing you to handle it.
When should I use satisfies vs a type annotation?
A type annotation (const x: T = …) widens the value to T. satisfies checks the value against T while keeping its specific literal types. Use satisfies when you want validation but also need the precise inferred types downstream (e.g. a config object whose exact keys matter).
What's the difference between conditional and mapped types?
Conditional types choose a type based on a condition (T extends U ? X : Y), often with infer to extract inner types. Mapped types transform a type by iterating its keys ({ [P in keyof T]: … }). Conditionals decide; mapped types reshape.
Should I use these everywhere?
No. Advanced types shine for reusable library types and encoding real invariants. For everyday application code, simple interfaces and unions are clearer. If a type needs a paragraph to explain, it's probably too clever for its context.
Related Topics
- TypeScript — The hub page
- TypeScript Basics — Fundamentals first
- JavaScript — The runtime underneath
- Forms & Validation — Runtime schemas vs compile-time types
- Code Quality — Type checking in CI