Types that make invalid states unrepresentable
A practical tour of modelling a domain so the compiler catches the bugs you'd otherwise ship.
The best bug is the one you cannot write down. When a type makes an invalid state unrepresentable, the compiler stops being a spell-checker and starts being a proof assistant — every illegal combination becomes a syntax error long before it becomes a support ticket.
The boolean trap
Consider a request modelled with a fistful of flags:
interface RequestState {
isLoading: boolean;
isError: boolean;
data: User | null;
error: Error | null;
}Four fields, sixteen combinations — and only four of them are real. What does
isLoading: true with a non-null error even mean? The type permits it, so
eventually some code path produces it, and now you are debugging a state that should
never have existed.
Make the illegal unspellable
A discriminated union collapses those sixteen ghosts down to the four states that actually occur:
type RequestState =
| { status: "idle" }
| { status: "loading" }
| { status: "success"; data: User }
| { status: "error"; error: Error };Now data exists only in the success branch and error only in the error
branch. There is no syntax for the contradiction. The bug class is gone — not caught,
not handled, gone.
This is the whole move, applied over and over: push correctness into the type so the machine enforces it for free, and spend your attention on the problems that are genuinely hard.