← All articles

TypeScript Tips That Changed How I Code

2 min readDevelopment

TypeScript Tips That Changed How I Code

TypeScript is powerful, but it's easy to fight against it. Here are patterns that made me embrace the type system instead of working around it.

1. Use Discriminated Unions for State

Instead of optional properties that can conflict, use discriminated unions:

type LoadingState = { status: 'loading' };
type SuccessState = { status: 'success'; data: User[] };
type ErrorState = { status: 'error'; error: string };

type State = LoadingState | SuccessState | ErrorState;

function render(state: State) {
  switch (state.status) {
    case 'loading':
      return <Spinner />;
    case 'success':
      return <UserList users={state.data} />;
    case 'error':
      return <Error message={state.error} />;
  }
}

2. Branded Types for Domain Safety

Prevent mixing up similar types:

type UserId = string & { __brand: 'UserId' };
type PostId = string & { __brand: 'PostId' };

function getUser(id: UserId) { /* ... */ }
function getPost(id: PostId) { /* ... */ }

// TypeScript will catch this:
getUser(postId); // Error!

3. Utility Types Are Your Friend

Pick, Omit, Partial, and Required can save you from writing duplicate types.

4. Const Assertions for Literal Types

const routes = ['/home', '/about', '/blog'] as const;
type Route = typeof routes[number]; // '/home' | '/about' | '/blog'

5. Use satisfies for Type Checking Without Widening

const config = {
  apiUrl: 'https://api.example.com',
  timeout: 5000,
} satisfies Config; // Checks types but preserves literal types

Start Small

You don't need to refactor everything at once. Start with new code and gradually improve existing code as you touch it.

Enjoyed this? Share it.