TypeScript Advanced Patterns
Unlock the full power of TypeScript with generics, conditional types, mapped types, and utility patterns.
Languages & FrameworksAdvanced20 min read
AI Summary
Quick context for TypeScript Advanced Patterns
TypeScript Advanced Patterns is a advanced guide in Languages & Frameworks. Unlock the full power of TypeScript with generics, conditional types, mapped types, and utility patterns.. Key topics: generics, patterns, types, typescript.
## Beyond the Basics
TypeScript's type system is Turing-complete, meaning you can express arbitrarily complex compile-time logic. This guide explores advanced patterns that make your code more precise, refactor-safe, and expressive — without sacrificing readability.
## Generics with Constraints
Generics allow you to write functions and types that work across multiple types while preserving type safety.
```typescript
// A generic function with a constraint
function pick(obj: T, keys: K[]): Pick {
const result = {} as Pick;
for (const key of keys) {
result[key] = obj[key];
}
return result;
}
const user = { id: 1, name: "Alice", email: "alice@example.com", age: 30 };
const summary = pick(user, ["id", "name"]);
// Type: { id: number; name: string }
```
The `extends keyof T` constraint ensures that only valid keys of `T` can be passed, giving you autocompletion and compile-time safety.
## Conditional Types
Conditional types let you create types that depend on type-level conditions, similar to a ternary operator at the type level.
```typescript
type IsString = T extends string ? true : false;
type A = IsString<"hello">; // true
type B = IsString<42>; // false
// Extract the return type of a function conditionally
type UnwrapPromise = T extends Promise ? U : T;
type Inner = UnwrapPromise>; // string
type Plain = UnwrapPromise; // number
```
The `infer` keyword introduces a type variable that TypeScript infers from a positional match. This is the foundation for extracting types from complex structures.
## Mapped Types
Mapped types transform every property in a type according to a rule, enabling powerful abstractions.
```typescript
// Make all properties optional and nullable
type Nullable = {
[K in keyof T]: T[K] | null;
};
interface Config {
host: string;
port: number;
debug: boolean;
}
type NullableC>;
// { host: string | null; port: number | null; debug: boolean | null }
// Create a type with boolean flags for every property
type Flags = {
[K in keyof T as `${string & K}Flag`]: boolean;
};
type C>;
// { hostFlag: boolean; portFlag: boolean; debugFlag: boolean }
```
## Template Literal Types
TypeScript 4.1 introduced template literal types, enabling string-level type manipulation.
```typescript
type EventName = "click" | "focus" | "blur";
type HandlerName = `on${Capitalize}`;
// "onClick" | "onFocus" | "onBlur"
// Build route parameter types from path patterns
type ExtractParams =
T extends `${string}:${infer Param}/${infer Rest}`
? { [K in Param | keyof ExtractParams]: string }
: T extends `${string}:${infer Param}`
? { [K in Param]: string }
: {};
type Params = ExtractParams<"/users/:userId/posts/:postId">;
// { userId: string; postId: string }
```
## Discriminated Unions
Discriminated unions use a common literal property to narrow types exhaustively, making state machines and API responses type-safe.
```typescript
type RequestState =
| { status: "idle" }
| { status: "loading" }
| { status: "success"; data-removed: T }
| { status: "error"; error: Error };
function renderState(state: RequestState) {
switch (state.status) {
case "idle":
return "Ready";
case "loading":
return "Loading...";
case "success":
return state.data.name; // TypeScript knows data exists
case "error":
return state.error.message; // TypeScript knows error exists
}
}
```
## The `satisfies` Operator
TypeScript 4.9 added `satisfies`, which validates that a value matches a type without widening it.
```typescript
type Route = {
path: string;
method: "GET" | "POST" | "PUT" | "DELETE";
handler: () => void;
};
// With type annotation: type is widened to Route[]
const routesA: Route[] = [
{ path: "/", method: "GET", handler: () => {} },
];
// With satisfies: literal types are preserved
const routesB = [
{ path: "/", method: "GET", handler: () => {} },
] satisfies Route[];
// routesB[0].method is "GET", not "GET" | "POST" | "PUT" | "DELETE"
```
## Branded Types
Branded types prevent accidental misuse of primitives that share the same underlying type.
```typescript
type UserId = string & { readonly __brand: unique symbol };
type OrderId = string & { readonly __brand: unique symbol };
function createUserId(id: string): UserId {
return id as UserId;
}
function fetchUser(id: UserId) { /* ... */ }
const userId = createUserId("usr_123");
const orderId = "ord_456" as OrderId;
fetchUser(userId); // OK
fetchUser(orderId); // Compile error: OrderId is not assignable to UserId
```
## Higher-Order Type Utilities
Combine built-in utility types to create expressive, reusable patterns.
```typescript
// Deep partial: make all nested properties optional
type DeepPartial = {
[K in keyof T]?: T[K] extends object ? DeepPartial : T[K];
};
// Make specific keys required while keeping the rest as-is
type RequireKeys = T & Required>;
// Omit by value type instead of key
type OmitByValue = {
[K in keyof T as T[K] extends ValueType ? never : K]: T[K];
};
interface Product {
id: number;
name: string;
price: number;
inStock: boolean;
}
type N boolean>;
// { id: number; name: string; price: number }
```
## Applying Patterns in Practice
These patterns shine in real codebases:
- **Discriminated unions** for API response handling and state management.
- **Branded types** for IDs in domain-driven design (pair with Prisma for database models).
- **Conditional and mapped types** for generating types from schemas or configuration objects.
- **Template literal types** for type-safe routing, event systems, and i18n keys.
Pair these techniques with strict ESLint rules and the `strict` compiler flag to catch errors early and maintain a robust codebase.
## Key Takeaways
- Use generics with `keyof` constraints for safe property access.
- Leverage `infer` in conditional types to extract types from complex structures.
- Discriminated unions enable exhaustive pattern matching at compile time.
- `satisfies` preserves literal types while still validating structure.
- Branded types prevent entire classes of runtime bugs by making invalid states unrepresentable.
Continue with related content
Suggested next reads and tools from the knowledge graph.
toolrelates to
ESLint
Pluggable linting utility for JavaScript and TypeScript
Languages & Frameworks#javascript#linting#code-quality
toolrelates to
Prisma
Next-generation ORM for TypeScript and JavaScript
Database#database#typescript#orm
toolrelates to
GitHub
Platform for version control and collaboration using Git