TypeScript Patterns
tsconfig Baseline
{
"compilerOptions": {
"strict": true,
"noUncheckedIndexedAccess": true,
"exactOptionalPropertyTypes": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"moduleResolution": "bundler",
"module": "ESNext",
"target": "ES2022",
"lib": ["ES2022", "DOM"],
"paths": { "@/*": ["./src/*"] }
}
}
Utility Types
// Pick, Omit, Partial, Required, Readonly
type CreateUser = Omit<User, 'id' | 'createdAt'>
type UpdateUser = Partial<Omit<User, 'id'>>
type ReadonlyUser = Readonly<User>
// ReturnType, Parameters, Awaited
type ApiResult = Awaited<ReturnType<typeof fetchUser>>
type FetchParams = Parameters<typeof fetchUser>[0]
// Record for index signatures
type FeatureFlags = Record<string, boolean>
type RouteHandlers = Record<string, (req: Request) => Response>
// Extract / Exclude for union manipulation
type StringOrNumber = string | number | boolean
type string> // string
type NoStrings = Exclude<StringOrNumber, string> // number | boolean
Discriminated Unions
type LoadingState<T> =
| { status: 'idle' }
| { status: 'loading' }
| { status: 'success'; data: T }
| { status: 'error'; error: Error }
function render<T>(state: LoadingState<T>) {
switch (state.status) {
case 'idle': return null
case 'loading': return '<Spinner />'
case 'success': return state.data // data is T here
case 'error': return state.error.message
}
}
Branded / Nominal Types
declare const _brand: unique symbol
type Brand<T, B> = T & { [_brand]: B }
type UserId = Brand<string, 'UserId'>
type OrderId = Brand<string, 'OrderId'>
const userId = 'u_123' as UserId
const orderId = 'o_456' as OrderId
// Compile error: cannot pass OrderId where UserId expected
function getUser(id: UserId): User { ... }
getUser(orderId) // TS Error
Generic Constraints
// Constrain to objects with specific shape
function pick<T extends object, K extends keyof T>(obj: T, keys: K[]): Pick<T, K> {
return keys.reduce((acc, k) => ({ ...acc, [k]: obj[k] }), {} as Pick<T, K>)
}
// Infer within conditionals
type Unpack<T> = T extends Array<infer U> ? U : T
type Item = Unpack<string[]> // string
type Same = Unpack<string> // string
// Template literal types
type EventName<T extends string> = `on${Capitalize<T>}`
type ClickHandler = EventName<'click'> // 'onClick'
Type Guards & Narrowing
// Custom type guard
function isUser(v: unknown): v is User {
return typeof v === 'object' && v !== null && 'id' in v && 'email' in v
}
// Assertion function
function assertDefined<T>(val: T | null | undefined, msg?: string): asserts val is T {
if (val == null) throw new Error(msg ?? 'Expected defined value')
}
// instanceof narrowing
function handleError(err: unknown): string {
if (err instanceof Error) return err.message
if (typeof err === 'string') return err
return 'Unknown error'
}
Mapped Types
// Make all properties nullable
type Nullable<T> = { [K in keyof T]: T[K] | null }
// Deep readonly
type DeepReadonly<T> = {
readonly [K in keyof T]: T[K] extends object ? DeepReadonly<T[K]> : T[K]
}
// Conditional mapped types
type OptionalIfNullable<T> = {
[K in keyof T as T[K] extends null | undefined ? K : never]?: T[K]
} & {
[K in keyof T as T[K] extends null | undefined ? never : K]: T[K]
}
Function Overloads
function parse(input: string): number
function parse(input: number): string
function parse(input: string | number): string | number {
if (typeof input === 'string') return Number(input)
return String(input)
}
Satisfies Operator (TS 4.9+)
const palette = {
red: [255, 0, 0],
green: '#00ff00',
} satisfies Record<string, string | number[]>
// palette.red is number[] (not string | number[])
palette.red.map(x => x * 2) // OK — type is preserved
Const Assertions
const ROLES = ['admin', 'editor', 'viewer'] as const
type Role = (typeof ROLES)[number] // 'admin' | 'editor' | 'viewer'
const config = {
endpoint: 'https://api.example.com',
timeout: 5000,
} as const
type Config = typeof config // all properties readonly literals
Error Handling Patterns
// Result type without throwing
type Result<T, E = Error> =
| { ok: true; value: T }
| { ok: false; error: E }
async function safeParseJson<T>(raw: string): Promise<Result<T>> {
try {
return { ok: true, value: JSON.parse(raw) as T }
} catch (e) {
return { ok: false, error: e instanceof Error ? e : new Error(String(e)) }
}
}
Module Augmentation
// Extend Express Request
declare global {
namespace Express {
interface Request {
user?: AuthUser
}
}
}
// Extend Window
interface Window {
analytics: AnalyticsInstance
}