TypeScript Tips for Everyday Use
Practical TypeScript patterns I reach for constantly — not the exotic stuff, just the bits that make daily coding better.
TypeScript can feel overwhelming with its endless type utilities and advanced patterns. This post focuses on the practical stuff — the patterns I actually reach for day to day.
Discriminated unions
When you have an object that can be in different states, discriminated unions make the type system work with you:
type Result<T> =
| { status: 'loading' }
| { status: 'error'; message: string }
| { status: 'success'; data: T };
function render<T>(result: Result<T>) {
switch (result.status) {
case 'loading': return 'Loading...';
case 'error': return result.message;
case 'success': return result.data;
}
}
TypeScript narrows the type inside each case block automatically.
The satisfies operator
satisfies lets you validate a type without widening it:
const palette = {
red: [255, 0, 0],
green: '#00ff00',
} satisfies Record<string, string | number[]>;
// palette.red is still typed as number[], not string | number[]
palette.red.map(x => x * 2); // works
Template literal types
type EventName = 'click' | 'focus' | 'blur';
type Handler = `on${Capitalize<EventName>}`; // 'onClick' | 'onFocus' | 'onBlur'
Incredibly useful for generating consistent string types from a base set.
as const for literal inference
const ROUTES = ['/', '/blog', '/about'] as const;
type Route = typeof ROUTES[number]; // '/' | '/blog' | '/about'
Without as const, TypeScript infers string[] instead of the literal tuple.
Optional chaining + nullish coalescing
Not new, but worth calling out:
const displayName = user?.profile?.displayName ?? 'Anonymous';
Cleaner than a chain of && checks and explicit null/undefined comparisons.
Conclusion
TypeScript rewards investment. The more precisely you model your domain with types, the more bugs the compiler catches before they reach production. Start with these patterns and work your way into the more advanced territory as the need arises.