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.

interfaceUser {
  id:number;
  name:string;
  email:string;
}

typeUserUpdate=Partial<User>;

Now every property in UserUpdate is optional.

constupdate:UserUpdate= {
  name:"Alex"
};

Step 2: Use Required to make properties mandatory

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

interfaceSettings {
  theme?:string;
  notifications?:boolean;
}

typeCompleteSettings=Required<Settings>;

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

Step 3: Use Readonly to prevent reassignment

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

interfaceProduct {
  id:number;
  price:number;
}

typeReadonlyProduct=Readonly<Product>;

constitem: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.

interfaceUser {
  id:number;
  name:string;
  email:string;
}

typeUserPreview=Pick<User,"id"|"name">;

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

Step 5: Use Omit to remove specific properties

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

interfaceUser {
  id:number;
  name:string;
  password:string;
}

typePublicUser=Omit<User,"password">;

constuser: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.

typeRoles="admin"|"editor"|"viewer";

typePermissions=Record<Roles,boolean>;

constpermissions: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

interfaceProfile {
  username:string;
  bio:string;
  avatarUrl:string;
}

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

Example 2: Public API response with Omit

interfaceAccount {
  id:number;
  email:string;
  password:string;
}

typePublicAccount=Omit<Account,"password">;

functiongetPublicAccount(account:Account):PublicAccount {
const { password, ...rest }=account;
returnrest;
}

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

typePublicAccountUpdate=Partial<Omit<Account,"password">>;

Example 3: Feature flags with Record

typeFeature="darkMode"|"betaAccess";

typeFeatureFlags=Record<Feature,boolean>;

constflags:FeatureFlags= {
  darkMode:true,
  betaAccess:false
};

Common mistakes and how to fix them

Mistake 1: Duplicating types instead of using utilities

You might write:

interfaceUserUpdate {
  id?:number;
  name?:string;
  email?:string;
}

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

Correct approach:

typeUserUpdate=Partial<User>;

Mistake 2: Expecting utility types to change runtime behavior

You might write:

constproduct: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