How to Use Utility Types in TypeScript

What you’ll build or solve

You’ll create derived types such as partial updates, read-only versions, or selected subsets of properties.

When this approach works best

Utility types work best when you:

  • Build forms where only some fields are required
  • Create update functions that accept partial data
  • Expose read-only versions of internal models

They also help when you want to reuse a base interface in multiple variations.

This is a bad idea if your types are already small and unlikely to change. In simple cases, writing a new interface may be clearer.

Prerequisites

  • TypeScript installed
  • A .ts file
  • Basic knowledge of interfaces and type aliases

Step-by-step instructions

Step 1: Use Partial to make properties optional

Partial<T> makes all properties in a type optional.

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

type UserUpdate = Partial<User>;

Now every property in UserUpdate is optional.

const update: UserUpdate = {
  name: "Alex"
};

Step 2: Use Required to make properties mandatory

Required<T> makes all properties required, even if they were optional.

interface Settings {
  theme?: string;
  notifications?: boolean;
}

type CompleteSettings = Required<Settings>;

const settings: CompleteSettings = {
  theme: "dark",
  notifications: true
};

Step 3: Use Readonly to prevent reassignment

Readonly<T> marks all properties as read-only.

interface Product {
  id: number;
  price: number;
}

type ReadonlyProduct = Readonly<Product>;

const item: ReadonlyProduct = {
  id: 1,
  price: 100
};

item.price = 200; // Error

Step 4: Use Pick to select specific properties

Pick<T, K> creates a new type with only selected keys.

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

type UserPreview = Pick<User, "id" | "name">;

const preview: UserPreview = {
  id: 1,
  name: "Jordan"
};

Step 5: Use Omit to remove specific properties

Omit<T, K> removes selected keys from a type.

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

type PublicUser = Omit<User, "password">;

const user: PublicUser = {
  id: 1,
  name: "Sam"
};

Step 6: Use Record to define object maps

Record<K, T> creates an object type with specific keys and value types.

type Roles = "admin" | "editor" | "viewer";

type Permissions = Record<Roles, boolean>;

const permissions: Permissions = {
  admin: true,
  editor: false,
  viewer: true
};

What to look for

  • Utility types wrap an existing type inside angle brackets
  • They do not modify the original interface
  • Combine utilities for more specific shapes, for example Partial<Omit<User, "password">>
  • Utility types affect compile-time checks only, Readonly does not block runtime mutation
  • Prefer reusing base types instead of duplicating interfaces

Examples you can copy

Example 1: Update function with Partial

interface Profile {
  username: string;
  bio: string;
  avatarUrl: string;
}

function updateProfile(id: number, updates: Partial<Profile>) {
  return { id, ...updates };
}

Example 2: Public API response with Omit

interface Account {
  id: number;
  email: string;
  password: string;
}

type PublicAccount = Omit<Account, "password">;

function getPublicAccount(account: Account): PublicAccount {
  const { password, ...rest } = account;
  return rest;
}

If you also need a “patch” shape for updates, combine utilities:

type PublicAccountUpdate = Partial<Omit<Account, "password">>;

Example 3: Feature flags with Record

type Feature = "darkMode" | "betaAccess";

type FeatureFlags = Record<Feature, boolean>;

const flags: FeatureFlags = {
  darkMode: true,
  betaAccess: false
};

Common mistakes and how to fix them

Mistake 1: Duplicating types instead of using utilities

You might write:

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

Why it breaks: When User changes, this copy can fall out of sync.

Correct approach:

type UserUpdate = Partial<User>;

Mistake 2: Expecting utility types to change runtime behavior

You might write:

const product: Readonly<Product> = { id: 1, price: 50 };
product.price = 60;

Why it breaks: Utility types affect compile-time checks only, not runtime behavior.

Correct approach: Keep the object immutable in your code. If you need mutation, use the base type or create a copy.

Troubleshooting

  • If TypeScript says a property is missing, confirm you are not using Required.
  • If a property becomes optional unexpectedly, check for Partial.
  • If a field is not available, confirm it was removed using Omit or excluded from Pick.
  • If a map object shows key errors, verify the union type used in Record.

Quick recap

  • Partial makes all properties optional
  • Required makes all properties mandatory
  • Readonly prevents reassignment
  • Pick selects specific keys
  • Omit removes specific keys
  • Record creates typed key-value maps