Featured image of post TypeScript Utility Types: Complete Guide with Real Examples

TypeScript Utility Types: Complete Guide with Real Examples

Complete guide to TypeScript utility types with real-world examples. Covers Partial, Required, Pick, Omit, Extract, Exclude, Record, NonNullable, Parameters, and more.

TypeScript provides a rich set of built-in utility types that transform and manipulate other types. Understanding these tools lets you write more expressive and type-safe code without reinventing the wheel.

Property Modification Types

These types modify the optionality, mutability, and requirement of object properties:

interface User {
  id: number;
  name: string;
  email?: string;
}

type PartialUser = Partial<User>;
// { id?: number; name?: string; email?: string; }

type RequiredUser = Required<User>;
// { id: number; name: string; email: string; }

type ImmutableUser = Readonly<User>;
// { readonly id: number; readonly name: string; readonly email?: string; }
TypeEffectCommon Use
Partial<T>All properties optionalPATCH request bodies
Required<T>All properties requiredForm submission validation
Readonly<T>All properties read-onlyConfiguration objects

Partial is especially useful for API update operations where clients should be able to send a subset of fields:

async function updateUser(id: number, changes: Partial<User>): Promise<User> {
  const res = await fetch(`/api/users/${id}`, {
    method: 'PATCH',
    body: JSON.stringify(changes),
  });
  return res.json();
}

Property Selection Types

Pick and Omit allow selecting or excluding specific properties from a type:

type UserView = Pick<User, 'id' | 'name'>;
// { id: number; name: string; }

type UserWithoutEmail = Omit<User, 'email'>;
// { id: number; name: string; }

Omit is ideal for removing sensitive fields:

type SafeUser = Omit<User, 'password' | 'token'>;

These types compose well with intersection types for fine-grained control:

type UserProfile = Pick<User, 'name' | 'email'> & { bio?: string };

Union Filtering Types

Extract and Exclude filter union members based on type compatibility:

type Status = 'idle' | 'loading' | 'success' | 'error';
type ActiveStatus = Exclude<Status, 'idle'>;
// 'loading' | 'success' | 'error'

type ApiEvent = 
  | { type: 'user.created'; user: User }
  | { type: 'user.deleted'; id: number }
  | { type: 'error'; message: string };

type UserEvents = Extract<ApiEvent, { type: `user.${string}` }>;
// { type: 'user.created'; user: User } | { type: 'user.deleted'; id: number }

NonNullable<T> strips null and undefined from a type:

type Maybe = string | null | undefined;
type Definite = NonNullable<Maybe>; // string

Object Construction Types

Record<K, T> creates an object type with specified key and value types:

type UserMap = Record<string, User>;
type RolePermissions = Record<'admin' | 'editor' | 'viewer', string[]>;

const permissions: RolePermissions = {
  admin: ['create', 'read', 'update', 'delete'],
  editor: ['create', 'read', 'update'],
  viewer: ['read'],
};

Function and Class Types

These types extract information from function signatures:

type Fn = (a: string, b: number) => boolean;

type Params = Parameters<Fn>;    // [string, number]
type Ret = ReturnType<Fn>;       // boolean
type FirstParam = Params[0];     // string

Awaited<T> unwraps Promise types recursively:

type AsyncResult = Awaited<Promise<Promise<string>>>; // string

InstanceType<T> extracts the instance type from a constructor:

class Service { /* ... */ }
type ServiceInstance = InstanceType<typeof Service>; // Service

Composition Patterns

Combining utility types creates expressive type definitions:

type Optional<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;
type UserUpdate = Optional<User, 'email'>;
// id and name required, email optional

For deep partial objects, a recursive mapped type does the job:

type DeepPartial<T> = {
  [P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P];
};

Mastering TypeScript utility types reduces boilerplate, improves type safety, and makes your code more expressive. Start with Partial, Pick, and Record, then gradually incorporate more advanced types as your type-level programming needs grow.