Kenneth Au

TypeScript Utility Types You Should Know

TypeScript includes built-in utility types that transform existing types into new ones. They save you from writing repetitive type definitions and make your code more expressive.

Partial and Required

Partial<T> makes all properties optional. Required<T> makes all properties required:

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

// All fields optional — useful for update operations
type UpdateUser = Partial<User>;
// { id?: number; name?: string; email?: string }

function updateUser(id: number, updates: UpdateUser): void {
  // Only send provided fields
}

Pick and Omit

Pick<T, K> selects specific properties. Omit<T, K> removes specific properties:

// Only expose safe fields
type UserPublic = Pick<User, 'id' | 'name'>;
// { id: number; name: string }

// Remove sensitive fields
type UserSafe = Omit<User, 'email'>;
// { id: number; name: string }

Record

Record<K, V> creates an object type with specific keys and a uniform value type:

type StatusCode = 200 | 404 | 500;

const statusMessages: Record<StatusCode, string> = {
  200: 'OK',
  404: 'Not Found',
  500: 'Internal Server Error',
};

// String-keyed records
const cache: Record<string, { value: unknown; expires: number }> = {};

Readonly

Readonly<T> makes all properties immutable:

const config: Readonly<DatabaseConfig> = {
  host: 'localhost',
  port: 5432,
};

// config.port = 3306; // Error: Cannot assign to 'port' because it is read-only

ReturnType and Parameters

ReturnType<T> extracts the return type of a function. Parameters<T> extracts parameter types as a tuple:

function createUser(name: string, age: number): User {
  return { id: 1, name, email: '' };
}

type UserResult = ReturnType<typeof createUser>;
// User

type CreateUserParams = Parameters<typeof createUser>;
// [string, number]

Extract and Exclude

Extract<T, U> keeps types that extend U. Exclude<T, U> removes types that extend U:

type Response = string | number | boolean | null;

type NonNull = Exclude<Response, null>;
// string | number | boolean

type OnlyString = Extract<Response, string>;
// string

Awaited

Awaited<T> unwraps a Promise type — useful for async function return types:

async function fetchData(): Promise<{ id: number; name: string }> {
  const res = await fetch('/api/data');
  return res.json();
}

type DataResult = Awaited<ReturnType<typeof fetchData>>;
// { id: number; name: string }

Combining Utilities

Real power comes from combining these:

interface Todo {
  id: number;
  title: string;
  completed: boolean;
  createdAt: Date;
  userId: number;
}

// Create DTO: omit auto-generated fields, make everything optional
type CreateTodo = Omit<Todo, 'id' | 'createdAt'>;
// { title: string; completed: boolean; userId: number }

// Update DTO: everything optional
type UpdateTodo = Partial<CreateTodo>;
// { title?: string; completed?: boolean; userId?: number }

// API response: readonly for consumers
type TodoResponse = Readonly<Todo>;

These utilities eliminate boilerplate and keep your types DRY. Instead of maintaining parallel type definitions that drift apart over time, derive them from the source of truth.