DEV Community

Atlas Whoff
Atlas Whoff

Posted on

TypeScript Utility Types: Mastering Pick, Omit, Partial, Required, and Record

TypeScript Utility Types: Mastering Pick, Omit, Partial, Required, and Record

TypeScript ships with powerful utility types that eliminate boilerplate.
Here's a practical guide to the ones you'll use daily.

Partial — Make All Properties Optional

interface User {
  id: string
  name: string
  email: string
  role: 'admin' | 'user'
}

// For update operations where any field can be changed
type UpdateUserDto = Partial<User>
// { id?: string; name?: string; email?: string; role?: 'admin' | 'user' }

async function updateUser(id: string, data: Partial<Omit<User, 'id'>>) {
  return db.user.update({ where: { id }, data })
}

updateUser('1', { name: 'New Name' })  // only name, valid
updateUser('1', { role: 'admin' })      // only role, valid
Enter fullscreen mode Exit fullscreen mode

Required — Make All Properties Required

interface Config {
  apiUrl?: string
  timeout?: number
  retries?: number
}

// After validation, all fields are present
type ValidatedConfig = Required<Config>
// { apiUrl: string; timeout: number; retries: number }

function validateConfig(config: Config): ValidatedConfig {
  return {
    apiUrl: config.apiUrl ?? 'https://api.example.com',
    timeout: config.timeout ?? 5000,
    retries: config.retries ?? 3,
  }
}
Enter fullscreen mode Exit fullscreen mode

Pick — Select Specific Properties

interface User {
  id: string
  name: string
  email: string
  passwordHash: string
  stripeCustomerId: string
}

// Safe to expose publicly
type PublicUser = Pick<User, 'id' | 'name' | 'email'>
// { id: string; name: string; email: string }

// Never expose passwordHash or stripeCustomerId
function toPublicUser(user: User): PublicUser {
  return { id: user.id, name: user.name, email: user.email }
}
Enter fullscreen mode Exit fullscreen mode

Omit — Exclude Specific Properties

// For create operations — DB generates the id
type CreateUserDto = Omit<User, 'id' | 'passwordHash'>
// { name: string; email: string; stripeCustomerId: string }

// Combine with Partial for flexible updates
type UpdateUserDto = Partial<Omit<User, 'id'>>
Enter fullscreen mode Exit fullscreen mode

Record — Object with Specific Key/Value Types

// Map of user roles to permissions
type Permission = 'read' | 'write' | 'delete' | 'admin'
type Role = 'viewer' | 'editor' | 'admin'

const rolePermissions: Record<Role, Permission[]> = {
  viewer: ['read'],
  editor: ['read', 'write'],
  admin: ['read', 'write', 'delete', 'admin'],
}

// Status map
type OrderStatus = 'pending' | 'processing' | 'shipped' | 'delivered' | 'cancelled'
const statusLabels: Record<OrderStatus, string> = {
  pending: 'Waiting for payment',
  processing: 'Preparing your order',
  shipped: 'On the way',
  delivered: 'Delivered',
  cancelled: 'Cancelled',
}
Enter fullscreen mode Exit fullscreen mode

Readonly — Immutable Type

const config: Readonly<Config> = {
  apiUrl: 'https://api.example.com',
  timeout: 5000,
  retries: 3,
}

config.timeout = 1000  // Error: Cannot assign to 'timeout' (readonly)
Enter fullscreen mode Exit fullscreen mode

ReturnType and Parameters

async function getUser(id: string) {
  return db.user.findUniqueOrThrow({ where: { id } })
}

// Infer the return type without duplicating it
type User = Awaited<ReturnType<typeof getUser>>

// Infer parameter types
type GetUserParams = Parameters<typeof getUser>
// [id: string]

// Useful for wrapping functions
function withLogging<T extends (...args: any[]) => any>(
  fn: T
): (...args: Parameters<T>) => ReturnType<T> {
  return (...args) => {
    console.log('Calling', fn.name, 'with', args)
    return fn(...args)
  }
}
Enter fullscreen mode Exit fullscreen mode

NonNullable

type MaybeUser = User | null | undefined
type DefinitelyUser = NonNullable<MaybeUser>  // User

// After null checks
function processUser(user: MaybeUser) {
  if (!user) throw new Error('User required')
  const definite: NonNullable<typeof user> = user  // User
}
Enter fullscreen mode Exit fullscreen mode

Extract and Exclude

type Status = 'active' | 'inactive' | 'pending' | 'banned'

type ActiveStatuses = Extract<Status, 'active' | 'pending'>
// 'active' | 'pending'

type AllowedStatuses = Exclude<Status, 'banned'>
// 'active' | 'inactive' | 'pending'
Enter fullscreen mode Exit fullscreen mode

The Ship Fast Skill Pack includes a /api skill that generates fully-typed DTOs using these patterns. $49 one-time.

Top comments (0)