Why Utility Types Matter
TypeScript ships with a set of built-in generic types that transform other types. Most developers know Partial and Required. Few use the full set โ which means they keep writing by hand what the compiler already knows how to do.
Here are the 10 that actually show up in production code.
1. Partial<T> โ Make All Fields Optional
interface User {
id: string
name: string
email: string
}
// For update functions โ you don't need to send every field
function updateUser(id: string, updates: Partial<User>) { ... }
2. Required<T> โ Make All Fields Required
The opposite of Partial. Useful when a type has optional fields at the source but you need everything present after validation.
interface Config {
host?: string
port?: number
timeout?: number
}
// After defaults are applied, everything is present
function applyDefaults(config: Config): Required<Config> {
return {
host: config.host ?? 'localhost',
port: config.port ?? 3000,
timeout: config.timeout ?? 5000,
}
}
3. Pick<T, K> โ Select Specific Fields
interface User {
id: string
name: string
email: string
passwordHash: string
createdAt: Date
}
// Never expose internal fields in API responses
type PublicUser = Pick<User, 'id' | 'name' | 'email'>
4. Omit<T, K> โ Remove Specific Fields
// Create without the generated fields
type CreateUserInput = Omit<User, 'id' | 'createdAt'>
function createUser(input: CreateUserInput): User { ... }
5. Record<K, V> โ Map of Keys to Values
Much cleaner than { [key: string]: V } and works with union keys:
type Status = 'pending' | 'active' | 'inactive'
type StatusConfig = Record<Status, { label: string; color: string }>
const config: StatusConfig = {
pending: { label: 'Pending', color: 'yellow' },
active: { label: 'Active', color: 'green' },
inactive: { label: 'Inactive', color: 'gray' },
}
// TypeScript will error if you miss a status
6. Readonly<T> โ Prevent Mutation
function processConfig(config: Readonly<Config>) {
// config.host = 'other' // Error: Cannot assign to 'host'
// Forces you to create new objects instead of mutating
}
7. ReturnType<T> โ Extract a Function's Return Type
async function getUser(id: string) {
return { id, name: 'Alice', role: 'admin' as const }
}
// Don't repeat yourself โ derive the type from the function
type UserResult = Awaited<ReturnType<typeof getUser>>
// { id: string; name: string; role: 'admin' }
This is invaluable when the return type is complex and you don't want to define it separately.
8. Parameters<T> โ Extract Function Parameters
function createOrder(userId: string, items: Item[], coupon?: string) { ... }
type OrderParams = Parameters<typeof createOrder>
// [userId: string, items: Item[], coupon?: string]
// Useful for wrapper/middleware functions
function loggedCreateOrder(...args: Parameters<typeof createOrder>) {
console.log('Creating order:', args)
return createOrder(...args)
}
9. NonNullable<T> โ Remove null and undefined
type MaybeUser = User | null | undefined
// After you've checked it's not null
type DefiniteUser = NonNullable<MaybeUser> // User
10. Extract<T, U> and Exclude<T, U>
type EventType = 'click' | 'hover' | 'focus' | 'blur' | 'keydown'
// Only keyboard events
type KeyboardEvent = Extract<EventType, 'keydown' | 'keyup'>
// 'keydown'
// Everything except keyboard events
type MouseEvent = Exclude<EventType, 'keydown' | 'keyup'>
// 'click' | 'hover' | 'focus' | 'blur'
Combining Utility Types
The real power comes from combining them:
// Read-only partial update (safe config merge)
type SafeConfigUpdate = Readonly<Partial<Config>>
// API response without sensitive fields, all required
type ApiUser = Required<Omit<User, 'passwordHash' | 'internalNotes'>>
// Function that accepts any subset of User fields
function updateProfile(userId: string, data: Partial<Pick<User, 'name' | 'email'>>) { ... }
Key Takeaways
Partial/Requiredโ toggle field optionalityPick/Omitโ select or remove fieldsRecordโ typed key-value maps (great with union keys)ReturnType/Parametersโ derive types from functions- Combine them โ the real utility comes from composition