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:
Learn TypeScript on Mimo
- 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
.tsfile - 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,
Readonlydoes 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
Omitor excluded fromPick. - If a map object shows key errors, verify the union type used in
Record.
Quick recap
Partialmakes all properties optionalRequiredmakes all properties mandatoryReadonlyprevents reassignmentPickselects specific keysOmitremoves specific keysRecordcreates typed key-value maps
Join 35M+ people learning for free on Mimo
4.8 out of 5 across 1M+ reviews
Check us out on Apple AppStore, Google Play Store, and Trustpilot