TypeScript Tricks I Use Daily
Most TypeScript tutorials stop at interfaces and generics. Here are the patterns I reach for daily that make codebases dramatically safer and more expressive.
Discriminated Unions Are Underused
If you're using a boolean flag to represent state, you're probably holding it wrong. Discriminated unions encode state transitions into the type system itself. The compiler tells you when you've forgotten a case.
type AsyncState =
| { status: 'idle' }
| { status: 'loading' }
| { status: 'success'; data: T }
| { status: 'error'; error: Error };
Template Literal Types
These sound academic until you use them for event names, CSS class generation, or API route typing. You can encode entire naming conventions in the type system.
Branded Types for Semantic Safety
A UserId and an OrderId are both strings. TypeScript doesn't care. Branded types make them incompatible, which catches entire categories of bugs at compile time rather than runtime.
The satisfies Operator
Added in TS 4.9 and still underused. It validates that a value matches a type without widening the inferred type. Perfect for config objects where you want both safety and precise inference.
Conditional Types in Practice
Deep conditional types look scary. In practice, 90% of use cases are: "if this prop is passed, require this other prop." The infer keyword unlocks extracting types from other types — essential for wrapping third-party library types.
TypeScript Performance in Large Codebases
When type checking takes more than 10 seconds, the team stops using it properly. Use tsc --generateTrace to profile slow types. The usual culprits: deeply recursive types, excessive union members, and circular imports.