Beyond Basic Types
Lorem ipsum dolor sit amet, consectetur adipiscing elit. TypeScript is more than just adding : string to your variables. The real power comes from patterns that make impossible states unrepresentable.
Discriminated Unions
One of the most useful patterns is the discriminated union. Instead of optional fields and null checks everywhere, you model your data as distinct states:
type ApiState<T> =
| { status: 'idle' }
| { status: 'loading' }
| { status: 'success'; data: T }
| { status: 'error'; error: string }
Now TypeScript knows exactly which fields exist in each state. No more undefined checks.
Branded Types
Sometimes you need to distinguish between values that share the same primitive type:
type UserId = string & { __brand: 'UserId' }
type PostId = string & { __brand: 'PostId' }
function getPost(postId: PostId) { /* ... */ }
You can't accidentally pass a UserId where a PostId is expected.
Const Assertions
Using as const creates narrow literal types that are useful for configuration objects:
const ROUTES = {
home: '/',
blog: '/blog',
about: '/about',
} as const
Builder Pattern
For complex object construction, a type-safe builder pattern ensures all required fields are set before building:
class QueryBuilder<T extends Record<string, unknown>> {
private params: Partial<T> = {}
where<K extends keyof T>(key: K, value: T[K]) {
this.params[key] = value
return this
}
build(): T {
return this.params as T
}
}
The Takeaway
Good TypeScript isn't about type gymnastics. It's about encoding business rules into the type system so errors are caught at compile time, not runtime.
Write types that tell a story about your domain.