Type-driven refactors that actually paid off
1 min
“Make illegal states unrepresentable” sounds like a slogan until a discriminated union deletes a whole class of bug. Three concrete before/afters from a real codebase.
The first was a request object with five optional fields and a comment explaining which combinations were actually valid. Nobody read the comment. Replacing it with a discriminated union over a status field meant the compiler enforced the combinations the comment used to describe, and an entire category of “why is this field undefined” bugs stopped shipping.
The second was a loading state modeled as three separate booleans — isLoading, isError, hasData — that could, and occasionally did, all be true at once. Collapsing them into a single tagged union with one state at a time removed the branch where the UI rendered a spinner over an error message, which had been a real bug in production for months before anyone traced it back.
The third was quieter: a function that accepted a plain string for a currency amount, which meant “10”, “10.00”, and “$10” were all valid inputs as far as the type system was concerned. A branded type that could only be constructed through a validated parser pushed the validation to the boundary once, instead of re-checking it in four call sites with four slightly different regexes.
None of these took more than an afternoon. The pattern in all three: find the comment that explains a constraint the types don’t enforce, and ask whether the type system can just enforce it.