Table of Contents
- Modifying Property Mutability
- Object Shape Manipulation
- Union Type Filtering
- Function Type Utilities
- Nullability Handling
- Advanced Combinations
- Best Practices
- Conclusion
- Reference
Modifying Property Mutability
These utility types adjust whether properties in a type are optional, required, or read-only.
Partial – Making Properties Optional
Description: Converts all properties of T to optional (adds ? modifier).
Syntax: Partial<T>
Use Case: When you need to update an object but don’t want to require all properties (e.g., patch operations).
Example:
Suppose you have a User interface with required properties, but you want a function to update only specific fields:
interface User {
id: number;
name: string;
email: string;
}
// Convert User properties to optional
type PartialUser = Partial<User>;
// Result: { id?: number; name?: string; email?: string }
// Update function using Partial<User>
function updateUser(user: User, changes: Partial<User>): User {
return { ...user, ...changes };
}
// Usage: Only update the email
const user: User = { id: 1, name: "Alice", email: "[email protected]" };
const updatedUser = updateUser(user, { email: "[email protected]" });
// { id: 1, name: "Alice", email: "[email protected]" }
Required – Enforcing Required Properties
Description: The opposite of Partial<T>; converts all optional properties of T to required (removes ? modifier).
Syntax: Required<T>
Use Case: When you need to ensure all properties of a type are provided (e.g., validating configs).
Example:
A Config interface with optional properties, but you need a strict version where all fields are required:
interface Config {
apiUrl?: string;
timeout?: number;
}
// Enforce all properties as required
type StrictConfig = Required<Config>;
// Result: { apiUrl: string; timeout: number }
// Function requiring StrictConfig
function initializeApp(config: StrictConfig) {
console.log("API URL:", config.apiUrl);
console.log("Timeout:", config.timeout);
}
initializeApp({ apiUrl: "https://api.example.com", timeout: 5000 }); // Valid
initializeApp({ apiUrl: "https://api.example.com" }); // Error: Property 'timeout' is missing
Readonly – Immutable Properties
Description: Converts all properties of T to read-only (adds readonly modifier).
Syntax: Readonly<T>
Use Case: Enforcing immutability (e.g., state objects that shouldn’t be modified directly).
Example:
Create an immutable Point type where coordinates can’t be reassigned:
interface Point {
x: number;
y: number;
}
// Make properties read-only
type ImmutablePoint = Readonly<Point>;
// Result: { readonly x: number; readonly y: number }
const point: ImmutablePoint = { x: 10, y: 20 };
point.x = 30; // Error: Cannot assign to 'x' because it is a read-only property
Object Shape Manipulation
These utilities help reshape object types by selecting, removing, or defining key-value pairs.
Record<K, T> – Dictionary-like Objects
Description: Constructs an object type with keys of type K and values of type T.
Syntax: Record<K, T> (where K must be a string, number, or symbol type).
Use Case: Defining dictionaries, maps, or lookup tables.
Example:
A role-permissions map where keys are role names and values are arrays of permissions:
type Role = "admin" | "editor" | "viewer";
type Permissions = string[];
// Define a record with Role keys and Permissions values
type RolePermissions = Record<Role, Permissions>;
const rolePermissions: RolePermissions = {
admin: ["create", "read", "update", "delete"],
editor: ["read", "update"],
viewer: ["read"],
};
// Accessing values is type-safe
console.log(rolePermissions.editor); // ["read", "update"]
Pick<T, K> – Selecting Properties
Description: Selects a subset of properties K from T (where K is a union of keys from T).
Syntax: Pick<T, K>
Use Case: Creating a simplified version of an interface (e.g., excluding sensitive data).
Example:
Extract only id and name from User to create a UserSummary:
interface User {
id: number;
name: string;
email: string;
password: string; // Sensitive!
}
// Pick 'id' and 'name' from User
type UserSummary = Pick<User, "id" | "name">;
// Result: { id: number; name: string }
// Safe to expose publicly
const userSummary: UserSummary = { id: 1, name: "Alice" };
Omit<T, K> – Removing Properties
Description: The opposite of Pick<T, K>; removes properties K from T.
Syntax: Omit<T, K>
Use Case: Excluding specific properties (e.g., removing sensitive fields like password).
Example:
Remove password from User to create a PublicUser:
// Omit 'password' from User
type PublicUser = Omit<User, "password">;
// Result: { id: number; name: string; email: string }
const publicUser: PublicUser = { id: 1, name: "Alice", email: "[email protected]" };
Union Type Filtering
These utilities work with union types to exclude or extract specific types.
Exclude<T, U> – Excluding Types from a Union
Description: Excludes types from T that are assignable to U.
Syntax: Exclude<T, U>
Use Case: Filtering union types to remove unwanted options.
Example:
Remove "a" and "c" from the union "a" | "b" | "c" | "d":
type Letters = "a" | "b" | "c" | "d";
type Excluded = Exclude<Letters, "a" | "c">;
// Result: "b" | "d" (only types not in "a"|"c" remain)
Extract<T, U> – Extracting Common Types
Description: Extracts types from T that are assignable to U (opposite of Exclude).
Syntax: Extract<T, U>
Use Case: Finding common types between two unions.
Example:
Extract types common to Letters and "a" | "c" | "e":
type CommonLetters = Extract<Letters, "a" | "c" | "e">;
// Result: "a" | "c" (types present in both unions)
Function Type Utilities
These utilities capture types from functions, such as return values or parameters.
ReturnType – Capturing Return Types
Description: Extracts the return type of a function T.
Syntax: ReturnType<T> (where T is a function type).
Use Case: Reusing a function’s return type without duplicating it.
Example:
Capture the return type of a fetchUser function:
function fetchUser(id: number): User {
return { id, name: "John", email: "[email protected]", password: "secret" };
}
// Extract the return type of fetchUser
type UserResponse = ReturnType<typeof fetchUser>;
// Result: User (matches the return type of fetchUser)
Parameters – Capturing Function Parameters
Description: Extracts the parameter types of a function T as a tuple.
Syntax: Parameters<T> (where T is a function type).
Use Case: Typing function arguments or creating parameter tuples.
Example:
Capture the parameters of fetchUser to type a helper function:
// Extract parameters of fetchUser as a tuple
type FetchUserParams = Parameters<typeof fetchUser>;
// Result: [number] (tuple with one number parameter)
// Use the parameters tuple to type a logging function
function logFetchParams(...params: FetchUserParams) {
console.log("Fetching user with ID:", params[0]);
}
logFetchParams(123); // Valid (parameter is a number)
logFetchParams("123"); // Error: Argument is not a number
Nullability Handling
NonNullable – Excluding Null/Undefined
Description: Removes null and undefined from T.
Syntax: NonNullable<T>
Use Case: Ensuring a type is guaranteed to have a value (e.g., after null checks).
Example:
Convert a nullable type to a non-nullable one:
type MaybeString = string | null | undefined;
// Remove null and undefined
type DefinitelyString = NonNullable<MaybeString>;
// Result: string
function printString(value: DefinitelyString) {
console.log(value.toUpperCase()); // Safe (no null/undefined)
}
printString("hello"); // Valid
printString(null); // Error: null is excluded
Advanced Combinations
Utility types shine when combined. Here are examples of composing them for complex transformations:
Example 1: Readonly + Pick
Create an immutable summary of a User:
type ReadonlyUserSummary = Readonly<Pick<User, "id" | "name">>;
// Result: { readonly id: number; readonly name: string }
Example 2: Partial + Omit
Create a partial update type excluding sensitive data:
type PartialPublicUser = Partial<Omit<User, "password">>;
// Result: { id?: number; name?: string; email?: string }
Best Practices
- Prioritize Readability: Avoid over-nesting utility types (e.g.,
Readonly<Partial<Omit<T, K>>>). Define intermediate types if clarity suffers. - Reduce Duplication: Use
ReturnTypeorParametersinstead of redefining function-related types. - Leverage Inference: Let TypeScript infer base types, then refine them with utilities.
- Know Your Use Case: Use
Pickfor including properties andOmitfor excluding—choose the one that makes intent clearer.
Conclusion
TypeScript’s utility types are indispensable for writing concise, maintainable, and type-safe code. By mastering utilities like Partial, Pick, ReturnType, and others, you can reduce boilerplate, enforce consistency, and handle complex type transformations with ease. Experiment with combining them to unlock even more powerful patterns, and always refer to the official docs for edge cases.