TypeScript Utility Types: The Complete Cheatsheet
TypeScript ships with powerful generic utilities that transform types. Knowing them saves hundreds of lines of manual type definitions.
Object Modification Utilities
### Partialinterface User { id: number; name: string; email: string }
type UserUpdate = Partial<User> // { id?: number; name?: string; email?: string }
function updateUser(id: number, changes: Partial<User>) { // changes can contain any subset of User fields } updateUser(1, { name: 'Alice' }) // valid — only name updateUser(1, { email: 'a@b.com', name: 'Alice' }) // also valid
### Requiredinterface Config { debug?: boolean timeout?: number retries?: number }
type FinalConfig = Required<Config> // { debug: boolean; timeout: number; retries: number }
### Readonlytype ReadonlyUser = Readonly<User> // { readonly id: number; readonly name: string; readonly email: string }
const user: ReadonlyUser = { id: 1, name: 'Alice', email: 'a@b.com' } user.name = 'Bob' // ❌ Error: Cannot assign to 'name' because it is read-only
### Recordtype StatusMap = Record<'active' | 'inactive' | 'banned', { count: number }> // { active: { count: number }; inactive: { count: number }; banned: { count: number } }
const statusCounts: Record<string, number> = {} statusCounts['active'] = 42 // safe
Key Selection Utilities
### Pickinterface User { id: number; name: string; email: string; password: string }
type PublicUser = Pick<User, 'id' | 'name' | 'email'> // { id: number; name: string; email: string } // password is excluded — safe to send to client
### Omittype UserWithoutPassword = Omit<User, 'password'> // { id: number; name: string; email: string }
// Common pattern: omit id when creating new entities type CreateUserInput = Omit<User, 'id'>
### Pick vs Omit — When to Use Which
- Use Pick when you know the small set of properties you WANT
- Use Omit when you know the small set of properties you DON'T WANT
- If the type has 20 fields and you want 18, Omit is cleaner than Pick
Union Type Utilities
### Excludetype Status = 'active' | 'inactive' | 'banned' | 'pending' type LiveStatus = Exclude<Status, 'banned' | 'pending'> // 'active' | 'inactive'
type NonNullableId = Exclude<string | null | undefined, null | undefined> // string
### Extracttype StringOrNum = string | number | boolean type OnlyStrings = Extract<StringOrNum, string> // string
// Useful for narrowing unions: type StringEvents = Extract<DOMEvent, MouseEvent | KeyboardEvent>
### NonNullabletype MaybeUser = User | null | undefined type DefiniteUser = NonNullable<MaybeUser> // User
function processUser(user: NonNullable<typeof maybeUser>) { // TypeScript knows user is defined here }
Function Type Utilities
### ReturnTypefunction createUser(name: string, email: string) { return { id: Math.random(), name, email, createdAt: new Date() } }
type User = ReturnType<typeof createUser> // { id: number; name: string; email: string; createdAt: Date } // No need to manually define User!
### Parametersfunction fetchData(url: string, options: RequestInit, timeout: number) { / / }
type FetchParams = Parameters<typeof fetchData> // [url: string, options: RequestInit, timeout: number]
// Forward exact same parameters to a wrapper function cachedFetch(...args: Parameters<typeof fetchData>) { return fetchData(...args) }
### Awaitedasync function getUser(): Promise<User> { / / }
type UserResult = Awaited<ReturnType<typeof getUser>> // User (Promise is unwrapped)
Combining Utilities
The real power comes from composing utilities:
// Patch endpoint: all fields optional except id (which is required)
type UserPatch = Required<Pick<User, 'id'>> & Partial<Omit<User, 'id'>>
// Read-only computed state type AppState = Readonly<{ user: NonNullable<User> settings: Required<Settings> cache: Record<string, unknown> }>
Practice TypeScript utility type questions at [JSPrep Pro](/auth).