
1. Branded Types for IDs
Never confuse a userId with a postId again:
type Brand<T, B> = T & { __brand: B };
type UserId = Brand<string, "UserId">;
type PostId = Brand<string, "PostId">;
function getUser(id: UserId) { /* ... */ }
const userId = "abc" as UserId;
const postId = "xyz" as PostId;
getUser(userId); // OK
getUser(postId); // Type error!
2. Template Literal Types
Create types from strings:
type HttpMethod = "GET" | "POST" | "PUT" | "DELETE";
type ApiRoute = `/api/${string}`;
type Endpoint = `${HttpMethod} ${ApiRoute}`;
// "GET /api/users" ✓
// "PATCH /api/users" ✗
3. satisfies for the best of both worlds
Validate the type without losing inference:
const config = {
port: 3000,
host: "localhost",
debug: true,
} satisfies Record<string, string | number | boolean>;
// config.port is still `number`, not `string | number | boolean`
4. Recursive Conditional Types
type DeepReadonly<T> = T extends object
? { readonly [K in keyof T]: DeepReadonly<T[K]> }
: T;
5. infer to extract types
type ReturnTypeOf<T> = T extends (...args: never[]) => infer R ? R : never;
type PromiseValue<T> = T extends Promise<infer V> ? V : T;
Mastering these patterns separates a good TypeScript dev from a great one.


