Cansu Arı Logo
Blog
How To Do?

Eliminate Code Duplication with TypeScript Utility Types

TypeScript’s unsung heroes: Partial, Pick, Omit. Less code, more safety!

  • #TypeScript
  • #Performance

Skip to content

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 for update, 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. 😊

All tags
  • TypeScript
  • Performance