TypeScript Utility Types for React: Partial, Omit, Pick
TypeScript includes built-in utility types that transform existing types into new shapes. Instead of rewriting interfaces, you use Partial to make all properties optional, Omit to remove properties, or Pick to select specific properties. These utilities make code more concise, reduce duplication, and keep types synchronized with their sources.
What Are TypeScript Utility Types?
Utility types are generic types in TypeScript's standard library that transform other types. They are used to create new types based on existing ones, reducing boilerplate. The most common utilities are part of the global globalThis namespace and are always available:
| Utility | Purpose | Syntax |
|---|---|---|
Partial<T> | Make all properties optional | Partial<User> |
Required<T> | Make all properties required | Required<User> |
Readonly<T> | Make all properties read-only | Readonly<User> |
Record<K, T> | Create an object with keys K and values T | Record<"a" | "b", string> |
Pick<T, K> | Select specific properties from T | Pick<User, "name" | "email"> |
Omit<T, K> | Remove specific properties from T | Omit<User, "password"> |
Extract<T, U> | Get types that are assignable to U | Extract<Status, "success"> |
Exclude<T, U> | Remove types assignable to U | Exclude<Status, "loading"> |
Understanding and using these utilities is essential for writing DRY (Don't Repeat Yourself) TypeScript code.
How Do You Use Partial and Required?
Partial<T> makes every property optional (adds ?). This is useful when you have an interface for complete objects but need a version where you only require some properties (like in update operations):
interface User {
id: number;
name: string;
email: string;
phone: string;
}
// For creating a user, all fields are required
const createUser = (data: User): Promise<User> => {
return fetch("/users", { method: "POST", body: JSON.stringify(data) }).then(
(r) => r.json()
);
};
// For updating a user, only some fields might be provided
const updateUser = (
id: number,
data: Partial<User>
): Promise<User> => {
return fetch(`/users/${id}`, {
method: "PATCH",
body: JSON.stringify(data),
}).then((r) => r.json());
};
// Call createUser: must provide all fields
await createUser({
id: 1,
name: "Alice",
email: "[email protected]",
phone: "555-1234",
});
// Call updateUser: can provide any subset of fields
await updateUser(1, { name: "Alice Smith" }); // OK: only name
await updateUser(1, { name: "Alice Smith", phone: "555-5678" }); // OK: multiple fields
Required<T> does the opposite—it removes all ? and makes every property required. This is useful when you have an optional interface but need a version where all properties are guaranteed:
interface PartialUser {
id?: number;
name?: string;
email?: string;
}
// Ensure all fields are present
function validateUser(user: Required<PartialUser>) {
// TypeScript knows all properties exist
console.log(user.id, user.name, user.email);
}
const user: PartialUser = { name: "Alice" };
// validateUser(user); // ERROR: id and email are missing
How Do You Use Pick and Omit?
Pick<T, K> selects only certain properties from a type. This is useful when you need a subset of an interface (like API response shapes):
interface User {
id: number;
name: string;
email: string;
password: string;
createdAt: Date;
updatedAt: Date;
}
// Response type: exclude sensitive fields (password)
type UserPublic = Pick<User, "id" | "name" | "email">;
// Same as writing:
// type UserPublic = {
// id: number;
// name: string;
// email: string;
// };
const user: UserPublic = {
id: 1,
name: "Alice",
email: "[email protected]",
// password is not included (and not allowed)
};
// API endpoint
function getUserResponse(id: number): Promise<UserPublic> {
return fetch(`/api/users/${id}`).then((r) => r.json());
}
Omit<T, K> removes specific properties from a type. This is the inverse of Pick—it is useful when you want most of the properties but need to exclude a few:
interface User {
id: number;
name: string;
email: string;
password: string;
createdAt: Date;
}
// Type without password and createdAt
type UserForUpdate = Omit<User, "password" | "createdAt">;
// Same as:
// type UserForUpdate = {
// id: number;
// name: string;
// email: string;
// };
const updateData: UserForUpdate = {
id: 1,
name: "Alice Smith",
email: "[email protected]",
};
function updateUser(data: UserForUpdate) {
return fetch("/api/users", { method: "PATCH", body: JSON.stringify(data) });
}
How Do You Use Record?
Record<K, T> creates an object type where all keys are K and all values are T. This is useful for dictionaries, lookup tables, and fixed-key objects:
// A lookup table: role -> description
type RoleDescriptions = Record<"admin" | "user" | "guest", string>;
const descriptions: RoleDescriptions = {
admin: "Full system access",
user: "Standard user access",
guest: "Read-only access",
// ERROR: any other key is not allowed
};
// A settings object with specific keys
type UserSettings = Record<"theme" | "language" | "notifications", boolean>;
const settings: UserSettings = {
theme: true,
language: true,
notifications: false,
};
// Dynamic lookup
function getRole(role: "admin" | "user" | "guest"): string {
const roles: Record<"admin" | "user" | "guest", string> = {
admin: "Administrator",
user: "Regular User",
guest: "Guest",
};
return roles[role];
}
Record is simpler and more type-safe than plain { [key: string]: T } when you know all the keys in advance.
How Do You Use Readonly?
Readonly<T> makes all properties immutable. This is useful for configuration objects or state that should not be modified:
interface AppConfig {
apiUrl: string;
timeout: number;
retries: number;
}
// Immutable configuration
const config: Readonly<AppConfig> = {
apiUrl: "https://api.example.com",
timeout: 5000,
retries: 3,
};
// ERROR: cannot assign to a readonly property
// config.timeout = 10000;
// For arrays, use readonly array syntax
type Numbers = readonly number[];
const nums: Numbers = [1, 2, 3];
// ERROR: cannot push to a readonly array
// nums.push(4);
Readonly is useful for configuration and props that you want to prevent accidental mutation. It does not prevent mutation at runtime but catches mistakes during development.
How Do You Combine Utility Types?
You can nest utility types to create complex transformations:
interface User {
id: number;
name: string;
email: string;
password: string;
role: "admin" | "user";
}
// Step 1: Remove sensitive fields
type SafeUser = Omit<User, "password">;
// Step 2: Make fields optional for updates
type UserUpdate = Partial<SafeUser>;
// Same as: type UserUpdate = {
// id?: number;
// name?: string;
// email?: string;
// role?: "admin" | "user";
// };
// Step 3: Make specific fields required
type UserCreate = Required<Pick<User, "name" | "email" | "role">>;
// Same as: type UserCreate = {
// name: string;
// email: string;
// role: "admin" | "user";
// };
// Use in functions
async function createUser(data: UserCreate): Promise<SafeUser> {
const user = await fetch("/users", {
method: "POST",
body: JSON.stringify(data),
}).then((r) => r.json());
return user;
}
async function updateUser(id: number, data: UserUpdate): Promise<SafeUser> {
const user = await fetch(`/users/${id}`, {
method: "PATCH",
body: JSON.stringify(data),
}).then((r) => r.json());
return user;
}
Combining utilities keeps your types synchronized and reduces duplication.
Key Takeaways
Partial<T>makes all properties optional; useful for update operations.Required<T>makes all properties required; useful for ensuring completeness.Pick<T, K>selects specific properties; useful for public API responses.Omit<T, K>removes specific properties; useful for hiding sensitive fields.Record<K, T>creates an object with fixed keys; useful for lookup tables.Readonly<T>makes properties immutable; useful for config and props.
Frequently Asked Questions
When should I use Omit vs Pick?
Use Pick when you have a few properties to include; use Omit when you have a few to exclude. If you are removing most properties, Pick is clearer. If you are removing a few, Omit is simpler:
// Pick: we want id, name, email (3 out of 6 fields)
type PublicUser = Pick<User, "id" | "name" | "email">;
// Omit: we want to hide password and createdAt (2 out of 6 fields)
type SafeUser = Omit<User, "password" | "createdAt">;
Can I use utility types with union types?
Yes, but the behavior might surprise you. Pick and Omit work on union types but apply to all members:
type AdminUser = { role: "admin"; permissions: string[] } & User;
type OmitPassword = Omit<AdminUser, "password">;
For complex transformations on unions, consider type guards instead.
How do I create a custom utility type?
Create a generic type alias that transforms other types:
// Utility: make all properties non-nullable
type NonNullable<T> = {
[K in keyof T]: Exclude<T[K], null | undefined>;
};
// Utility: make all properties a specific type
type MapToString<T> = {
[K in keyof T]: string;
};
This is an advanced topic (mapped types), covered in TypeScript docs.
Should I use utility types for every interface?
No. Use utility types when they reduce duplication and improve clarity. Simple interfaces without transformations do not need them. Use utility types strategically to keep your codebase DRY.