TypeScript Advanced: The Type System
Type-Safe Patterns
In this lesson you will
- Check a value against a type without widening it using satisfies
- Prevent mix-ups between similar values with branded types
- Write assertion functions and handle unknown input safely
Introduction
Good types do more than describe code. They make a whole class of mistakes impossible. This lesson collects the patterns that experienced TypeScript developers reach for most often.
The annotation problem
Annotating a variable checks it, but it also widens the type to the annotation, throwing away detail TypeScript knew.
We know red is a string, but the annotation made TypeScript forget.
satisfies: check without widening
satisfies checks a value against a type but keeps the specific type that was inferred.
It still catches mistakes in the value:
Quick quiz
What does satisfies do that a plain type annotation does not?
as const
as const freezes a literal to its exact values, which are then also readonly. It is excellent for fixed lists and configuration.
Branded types
Two values can have the same type but mean different things. A user id and an order id are both numbers, yet mixing them up is a bug TypeScript normally cannot see.
A brand adds a phantom marker that exists only in the type system:
At runtime they are plain numbers. The brand costs nothing, yet the compiler now refuses to mix them up. Create branded values in one place only, ideally after validating them.
Assertion functions
An assertion function throws if a condition fails, and tells TypeScript the condition holds afterwards. Its return type is asserts ....
Make illegal states unrepresentable
The strongest habit is to design types so that wrong combinations cannot even be written. Compare:
Quick quiz
Why use a branded type for ids?
Try it yourself
Using satisfies, define a settings object so that settings.mode has the exact type "light" | "dark" (not string) and the object is still checked to match Settings. Then call lightOnly(settings.mode), which accepts only "light": it should be an error, since the mode could be dark.
Summary
satisfiesvalidates a value against a type without widening what TypeScript knows about it.as constfreezes literals so you can derive precise types from data.- Branded types stop similar-looking values from being mixed up, at no runtime cost.
- Assertion functions check at runtime and narrow the type afterwards. Prefer union designs where invalid states cannot be written.
Lesson complete
+20 XP
Nice work. Keep going while it is fresh.