TypeScript Tips for Better Developer Experience
TypeScript Tips for Better Developer Experience
TypeScript is more than "JavaScript with types". These are the patterns I keep reaching for day to day.
as const: Narrow, Don't Widen
By default TypeScript widens object values to their general types. Most of the time that's fine — until you want to derive a union from those values.
const routes = {
home: '/',
blog: '/blog',
about: '/about'
} as const;
type Route = (typeof routes)[keyof typeof routes];
// type Route = "/" | "/blog" | "/about"Without as const you get string. With it, you get the exact values — and autocomplete that actually means something.
Discriminated Unions: Represent Your State Honestly
Optional properties spread across an interface are a lie. If data only exists when a request succeeds and error only exists when it fails, say so:
type State =
| { status: 'idle' }
| { status: 'loading' }
| { status: 'success'; data: string[] }
| { status: 'error'; error: Error };Narrow on status and TypeScript knows exactly what's available. No more defensive ?. everywhere.
In my case I use this pattern constantly for async state — it makes impossible states actually impossible.
Template Literal Types: String Logic Without the Repetition
This one comes up less often, but it earns its place. You can build string shapes at the type level:
type EventName<T extends string> = `on${Capitalize<T>}`;
type ClickEvent = EventName<'click'>; // "onClick"It gets more useful when you're generating unions from other unions — valid event names, CSS property keys, whatever your API surface looks like.
Three patterns, zero magic. Just TypeScript doing what it's good at.
Happy coding! 👋