5 TypeScript Utility Types You Should Know

TypeScript’s type system becomes truly powerful when you discover utility types—built-in helpers that transform existing types without rewriting them. If you’ve ever found yourself duplicating interfaces or struggling with type modifications, these five essential utility types will change how you write TypeScript code.
Key Takeaways
- Utility types transform existing types without rewriting them, reducing code duplication
- Partial makes all properties optional, perfect for update operations
- Required enforces all properties, ensuring complete data structures
- Pick and Omit help create focused interfaces by selecting or excluding properties
- Readonly prevents accidental mutations, essential for immutable data patterns
What Are TypeScript Utility Types and Why They Matter
Utility types are TypeScript’s pre-built generic types that construct new types by transforming existing ones. Instead of manually creating variations of your interfaces, these utilities let you derive new types with specific modifications—making your code more maintainable and reducing type duplication.
We’ll explore five fundamental utility types that solve common real-world problems: Partial, Required, Pick, Omit, and Readonly. Each addresses specific scenarios you encounter daily in TypeScript development.
Partial<T>: Making All Properties Optional in TypeScript
The Partial utility type transforms all properties of a type to optional, perfect for update operations where you only modify specific fields.
When to Use Partial for Form Updates
Consider a user profile update where users can change individual fields without sending the entire object:
interface User {
id: string;
name: string;
email: string;
avatar: string;
lastLogin: Date;
}
function updateUserProfile(userId: string, updates: Partial<User>) {
// Only update provided fields
return api.patch(`/users/${userId}`, updates);
}
// Usage - only updating email
updateUserProfile("123", { email: "new@email.com" });
Partial Type Example with Real-World Code
React state updates benefit significantly from Partial:
const [user, setUser] = useState<User>(initialUser);
const updateUser = (updates: Partial<User>) => {
setUser(prev => ({ ...prev, ...updates }));
};
// Update only specific fields
updateUser({ name: "Jane Doe", avatar: "new-avatar.jpg" });
Required<T>: Enforcing All Properties in TypeScript
Required does the opposite of Partial—it makes all optional properties mandatory, ensuring complete data structures.
Converting Optional to Required Properties
This proves invaluable for configuration validation:
interface DatabaseConfig {
host?: string;
port?: number;
username?: string;
password?: string;
}
// Ensure all config values exist before connecting
type ValidatedConfig = Required<DatabaseConfig>;
function connectDatabase(config: ValidatedConfig) {
// All properties guaranteed to exist
return createConnection(config);
}
Required Type in Action
Form submission often requires complete data:
interface RegistrationForm {
username?: string;
email?: string;
password?: string;
terms?: boolean;
}
type CompleteRegistration = Required<RegistrationForm>;
function submitRegistration(data: CompleteRegistration) {
// All fields must be filled
api.post('/register', data);
}
Discover how at OpenReplay.com.
Pick<T, K>: Selecting Specific Properties from Types
Pick creates a new type by selecting only specified properties, ideal for creating focused interfaces.
Creating Focused Type Subsets with Pick
Extract only what you need for specific components:
interface Article {
id: string;
title: string;
content: string;
author: string;
publishedAt: Date;
tags: string[];
}
// Card component only needs these fields
type ArticlePreview = Pick<Article, 'id' | 'title' | 'author'>;
function ArticleCard({ article }: { article: ArticlePreview }) {
return (
<div>
<h3>{article.title}</h3>
<p>by {article.author}</p>
</div>
);
}
Pick vs Omit: Choosing the Right Approach
Use Pick when you need few properties, Omit when excluding few properties:
// Pick: When you need 2-3 properties from a large type
type UserSummary = Pick<User, 'id' | 'name'>;
// Omit: When you need most properties except a few
type PublicUser = Omit<User, 'password'>;
Omit<T, K>: Excluding Properties from TypeScript Types
Omit creates a type by excluding specified properties, perfect for removing sensitive or internal fields.
Removing Sensitive Data with Omit
Protect sensitive information in API responses:
interface UserAccount {
id: string;
email: string;
password: string;
creditCard: string;
publicProfile: boolean;
}
// Remove sensitive fields for public API
type PublicUserData = Omit<UserAccount, 'password' | 'creditCard'>;
function getPublicProfile(userId: string): Promise<PublicUserData> {
return api.get(`/users/${userId}/public`);
}
Omit Utility Type Best Practices
Combine with other utilities for powerful patterns:
// Remove internal fields and make remaining optional
type UserUpdatePayload = Partial<Omit<User, 'id' | 'createdAt'>>;
Readonly<T>: Creating Immutable Types in TypeScript
Readonly makes all properties immutable, preventing accidental mutations and enforcing data integrity.
Preventing Accidental Mutations with Readonly
Essential for state management and configuration:
interface AppConfig {
apiEndpoint: string;
version: string;
features: string[];
}
type ImmutableConfig = Readonly<AppConfig>;
const config: ImmutableConfig = {
apiEndpoint: "https://api.example.com",
version: "1.0.0",
features: ["auth", "payments"]
};
// TypeScript error: Cannot assign to 'apiEndpoint' because it is a read-only property
config.apiEndpoint = "https://new-api.com"; // ❌
Readonly Properties and React Props
Ensure props remain unchanged:
type UserCardProps = Readonly<{
user: User;
onSelect: (id: string) => void;
}>;
function UserCard({ user, onSelect }: UserCardProps) {
// Props are immutable within component
return <div onClick={() => onSelect(user.id)}>{user.name}</div>;
}
Combining Multiple Utility Types for Advanced Patterns
Utility types become even more powerful when combined:
interface DatabaseRecord {
id: string;
createdAt: Date;
updatedAt: Date;
deletedAt?: Date;
data: Record<string, unknown>;
}
// Create a type for new records: no timestamps, partial data
type NewRecord = Omit<DatabaseRecord, 'id' | 'createdAt' | 'updatedAt'> & {
data: Partial<DatabaseRecord['data']>;
};
// Update type: everything optional except ID
type UpdateRecord = Partial<Omit<DatabaseRecord, 'id'>> & Pick<DatabaseRecord, 'id'>;
Conclusion
These five utility types—Partial, Required, Pick, Omit, and Readonly—form the foundation of effective TypeScript development. They eliminate repetitive type definitions, enforce type safety, and make your code more maintainable.
Start incorporating these patterns into your codebase. Begin with Partial for update operations, use Omit to create safe public interfaces, and leverage Readonly to prevent bugs. As you grow comfortable, combine them to create sophisticated type transformations that perfectly match your application’s needs.
FAQs
Yes, you can nest utility types to create complex transformations. For example, Partial and Omit work well together to create update payloads that exclude certain fields while making others optional.
No, utility types are purely compile-time constructs. TypeScript removes all type information during compilation, so utility types have zero runtime overhead.
Interface extension creates new types by adding properties, while utility types transform existing types. Utility types offer more flexibility for modifying types you don't control or creating variations of existing types.
Yes, TypeScript allows you to create custom utility types using mapped types, conditional types, and template literal types. Start with built-in utilities and create custom ones when you identify repeated patterns.
Complete picture for complete understanding
Capture every clue your frontend is leaving so you can instantly get to the root cause of any issue with OpenReplay — the open-source session replay tool for developers. Self-host it in minutes, and have complete control over your customer data.
Check our GitHub repo and join the thousands of developers in our community.