JS / TS

5 TypeScript Utility Types That Save Hours Every Week

3 min read

Most TypeScript codebases I audit have the same smell: near-duplicate interfaces. User, UserUpdate, UserPreview, UserFormValues — four hand-written types drifting apart one field at a time. Utility types exist to kill exactly this. They're built into the language, they're one line each, and these five earn their keep weekly.

All examples build on one interface:

interface User {
  id: string;
  name: string;
  email: string;
  role: 'admin' | 'member';
  createdAt: Date;
}

Partial — PATCH payloads

Partial<User> makes every field optional. Its natural habitat is update operations, where the caller sends only what changed:

async function updateUser(id: string, changes: Partial<User>) {
  return api.patch(`/users/${id}`, changes);
}
 
updateUser('u_1', { name: 'New Name' }); // ✓
updateUser('u_1', { nmae: 'typo' }); // ✗ compile error

Without it, teams either write a duplicate UserUpdate interface (drift risk) or type the payload as any (no checking at all). Partial gives you the update type derived from the source of truth — change User, and every update site stays correct.

Pick and Omit — shaping API responses

Pick keeps only the listed fields; Omit removes them. They shine anywhere a subset of a type crosses a boundary:

// A list endpoint returns lightweight previews:
type UserPreview = Pick<User, 'id' | 'name'>;
 
// A public API response must never include internal fields:
type PublicUser = Omit<User, 'createdAt'>;

The rule of thumb I give reviewers: Pick when the subset is small, Omit when the exclusion is small. Either way, the subset can't silently drift from the real type — rename name in User and Pick<User, 'name'> fails to compile until you fix it.

Record — typed lookup objects

Record<K, V> types an object as a map from keys to values. It's the fix for the untyped lookup-table pattern:

const roleLabels: Record<User['role'], string> = {
  admin: 'Administrator',
  member: 'Team member',
};

Two things happen here that a plain object literal doesn't give you. Add a new role to the union — say 'viewer' — and this object fails to compile until you add its label. Typo a key and you're caught immediately. I use this constantly for status→color maps, route→permission tables, and config keyed by environment.

ReturnType — stop duplicating what functions already know

ReturnType<typeof fn> extracts a function's return type. It removes the need to hand-maintain a type that some function already defines implicitly:

function buildSearchParams(query: string, page: number) {
  return { q: query.trim(), page, perPage: 20 };
}
 
// One source of truth — no separate SearchParams interface to maintain:
type SearchParams = ReturnType<typeof buildSearchParams>;
 
function serialize(params: SearchParams): string {
  return new URLSearchParams(params as never).toString();
}

This is especially valuable with factory functions and store creators, where the return shape is rich and writing it manually would be both tedious and instantly stale.

Quick reference

UtilityOne-liner
Partial<T>All fields optional — update/PATCH payloads
Pick<T, K>Keep only listed fields — previews, DTOs
Omit<T, K>Remove listed fields — strip internal data
Record<K, V>Typed lookup object — label maps, config tables
ReturnType<F>Extract a function's return type — one source of truth

The shared theme: derive types instead of duplicating them. Every hand-copied interface is a future bug where two definitions disagree; every derived type updates itself. Once this clicks, you'll start seeing the same idea everywhere in TypeScript — it's the foundation for patterns like discriminated unions and the features I covered in what I actually use every week.

Found this useful?

Share

I post shorter takes on javascript & typescript and frontend leadership on LinkedIn — follow along there, or get in touch if you're working on something related.