Fewer Lines, Stronger Types: A Utility Types Guide
Code duplication (violating DRY) is every developer’s nightmare. It looks like copy-paste heaven, then turns into refactor hell. 😊 Thankfully, TypeScript says you’re not alone and hands you a set of small but mighty tools: Utility Types!
These helpers let you transform, simplify, and reuse types instead of redefining them. Cleaner code and stronger type safety—less code, more peace of mind.
Partial
Makes all properties of a type optional. Perfect forupdate, patch, or form handlers.type User = { id: number; name: string; age: number };
const updateUser = (user: Partial<User>) => {
// only changed fields are required
};
Now updateUser({ name: 'Cansu' }) is valid because Partial makes every User field optional.
Bonus: for deep structures, create a custom DeepPartial.
Pick
Use when you want to select only certain fields from a type.type User = { id: number; name: string; age: number; email: string };
type UserPreview = Pick<User, 'id' | 'name'>;
UserPreview includes only id and name—handy for list views and simple cards.
Omit
Pick’s sibling, but in reverse—exclude certain fields.type UserWithoutAge = Omit<User, 'age'>;
Great for hiding sensitive or unnecessary fields from UIs.
Record
Build a key–value map with strict keys.type Roles = 'admin' | 'user';
const permissions: Record<Roles, boolean> = {
admin: true,
user: false,
};
Access is type-checked—TypeScript will bark (lovingly 💙) at invalid keys.
Readonly and Required
Readonly<T> → Make objects immutable. Required<T> → Turn optional fields into required ones.Great for locking down API responses or configs.
type Config = { apiKey?: string; version?: number };
type StrictConfig = Required<Readonly<Config>>;
Now nothing’s missing and nothing can be changed—your TypeScript bodyguard.
ReturnType and Parameters
Infer a function’s return or parameter types automatically.function createUser(name: string, age: number) {
return { name, age, active: true };
}
type UserReturn = ReturnType<typeof createUser>; // { name: string; age: number; active: boolean }
Parameters<typeof createUser> yields [string, number]. No more mismatched signatures.
Exclude and Extract
Opposite helpers for unions:Exclude<T, U>→ remove U from T.Extract<T, U>→ keep only members of T assignable to U.
type Status = 'pending' | 'success' | 'error';
type NonError = Exclude<Status, 'error'>; // 'pending' | 'success'
Fine-tune unions with precision.
Why Use Them?
- Dramatically reduces duplication.
- Improves type safety.
- Lowers refactor risk.
- Simplifies complex types.
- Keeps shared types sustainable in large teams.
- And most importantly: prevents “Why did we re-write this type?” crises. 🙃
Real-World Example
Consider a generic API response:type ApiResponse<T> = {
data: T;
status: number;
error?: string;
};
Use it across hundreds of endpoints: ApiResponse<User>, ApiResponse<Product>, etc. Utility Types are gold for flexible, modular systems like this.
Conclusion
Utility Types are TypeScript’s hidden heroes. Less code, more order; fewer bugs, more calm. Readability goes up, refactor anxiety goes down, and as your project scales, your type system scales with it.Using Utility Types isn’t just smart—it’s laziness done right. 😊