intermediate Step 11 of 18

Utility Types

TypeScript Programming

Utility Types

TypeScript ships with a collection of built-in utility types that transform existing types into new ones. These generic type helpers eliminate repetitive type definitions and make your code more maintainable. Instead of creating separate interfaces for every variation of a data model (create form, update form, API response, display model), you can derive them from a single source type using utility types. Mastering these utilities is a hallmark of proficient TypeScript development and dramatically reduces type definition boilerplate.

Partial and Required

Partial<T> makes all properties of T optional. Required<T> does the opposite — it makes all properties required. These are commonly used for update operations and configuration objects.

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

// Partial — all properties become optional
type UserUpdate = Partial<User>;

function updateUser(id: number, changes: UserUpdate): void {
  console.log(`Updating user ${id}:`, changes);
}

// Only need to pass changed fields
updateUser(1, { name: "Alice Updated" });
updateUser(1, { email: "new@email.com", avatar: "pic.jpg" });

// Required — all properties become required (including avatar)
type StrictUser = Required<User>;

const user: StrictUser = {
  id: 1,
  name: "Bob",
  email: "bob@test.com",
  avatar: "default.png", // Required! Cannot be omitted
};

Pick and Omit

Pick<T, K> creates a type with only the specified properties from T. Omit<T, K> creates a type with all properties except the specified ones. These are perfect for creating view models or API payloads.

interface BlogPost {
  id: number;
  title: string;
  body: string;
  authorId: number;
  createdAt: Date;
  updatedAt: Date;
  publishedAt: Date | null;
}

// Pick only the fields needed for a list view
type PostSummary = Pick<BlogPost, "id" | "title" | "createdAt">;

const summary: PostSummary = {
  id: 1,
  title: "TypeScript Tips",
  createdAt: new Date(),
};

// Omit internal fields for API creation payload
type CreatePostPayload = Omit<BlogPost, "id" | "createdAt" | "updatedAt">;

const payload: CreatePostPayload = {
  title: "New Post",
  body: "Content here...",
  authorId: 42,
  publishedAt: null,
};

Record

Record<K, V> creates an object type with keys of type K and values of type V. It is useful for dictionaries, lookup tables, and maps.

// Simple dictionary
type StatusLabels = Record<string, string>;

const labels: StatusLabels = {
  active: "Active",
  inactive: "Inactive",
  pending: "Pending Review",
};

// Record with a literal union for keys
type Role = "admin" | "editor" | "viewer";

type Permissions = Record<Role, string[]>;

const rolePermissions: Permissions = {
  admin: ["read", "write", "delete", "manage"],
  editor: ["read", "write"],
  viewer: ["read"],
};

// Record for grouping data
type GroupedUsers = Record<string, User[]>;

const departments: GroupedUsers = {
  engineering: [{ id: 1, name: "Alice", email: "a@test.com" }],
  marketing: [{ id: 2, name: "Bob", email: "b@test.com" }],
};

Readonly and Extract/Exclude

Readonly<T> makes all properties readonly. Extract and Exclude operate on union types to include or remove specific members.

// Readonly — prevents mutation
type ImmutableUser = Readonly<User>;
const frozenUser: ImmutableUser = { id: 1, name: "Alice", email: "a@test.com" };
// frozenUser.name = "Bob"; // Error: Cannot assign to 'name' (read-only)

// Extract — keep union members assignable to a type
type AllTypes = string | number | boolean | null | undefined;
type Primitives = Extract<AllTypes, string | number>; // string | number

// Exclude — remove union members assignable to a type
type NonNullable_ = Exclude<AllTypes, null | undefined>; // string | number | boolean

// ReturnType — extract the return type of a function
function fetchUsers(): Promise<User[]> {
  return fetch("/api/users").then(r => r.json());
}
type FetchResult = ReturnType<typeof fetchUsers>; // Promise<User[]>
Tip: Combine utility types for powerful transformations. For example, Partial<Omit<User, "id">> creates a type where all fields except id are optional — perfect for a PATCH endpoint payload.

Key Takeaways

  • Partial<T> makes all properties optional; Required<T> makes them all required.
  • Pick<T, K> selects specific properties; Omit<T, K> excludes specific properties.
  • Record<K, V> creates typed dictionary/map objects with specified key and value types.
  • Readonly<T> prevents mutation of all properties at compile time.
  • Utility types can be composed together for complex, precise type transformations.