# TypeScript Patterns I Actually Use

By Mehedi Hasan Shifat · 2025-11-15 · TypeScript, JavaScript

> Not the advanced wizardry — just the practical patterns that show up in real codebases and make the type system work for you.

There's a gap between "TypeScript exists" and "TypeScript is making my codebase meaningfully safer." These are the patterns that close that gap in day-to-day work.

## Discriminated Unions for State

Instead of a mess of optional fields, model mutually exclusive states explicitly:

```typescript
type RequestState<T> =
  | { status: "idle" }
  | { status: "loading" }
  | { status: "success"; data: T }
  | { status: "error"; error: string };
```

Now exhaustive `switch` statements work and TypeScript narrows `data` and `error` to the right variants automatically.

## `satisfies` for Config Objects

`satisfies` validates against a type without widening — you keep the literal types:

```typescript
const routes = {
  home: "/",
  blog: "/blog",
  contact: "/contact",
} satisfies Record<string, string>;

// routes.home is typed as "/" not string
```

This is great for config objects where you want both validation and autocompletion on the literal values.

## `Awaited<ReturnType<...>>`

When you need the resolved return type of an async function without duplicating a type definition:

```typescript
async function getUser(id: string) {
  return db.user.findUnique({ where: { id } });
}

type User = Awaited<ReturnType<typeof getUser>>;
```

## Branded Types for IDs

Plain `string` types for IDs are too permissive. Brand them to prevent mixing up `UserId` and `PostId`:

```typescript
type UserId = string & { readonly _brand: "UserId" };
type PostId = string & { readonly _brand: "PostId" };

function createUserId(id: string): UserId {
  return id as UserId;
}
```

Now passing a `PostId` where a `UserId` is expected is a type error.

## `infer` for Extracting Types

Useful when you need to pull a type out of a generic:

```typescript
type UnpackPromise<T> = T extends Promise<infer U> ? U : T;
type UnpackArray<T> = T extends Array<infer U> ? U : T;
```

## Closing Thoughts

The goal isn't type gymnastics — it's catching real bugs. Discriminated unions and branded types give you the most safety per line of type code. Use the rest when the problem actually calls for it.
