Skip to content
← Writing
TypeScriptDXTips

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! 👋

Ship it, learn it, repeat.