• typescript
  • types

The TypeScript utility types I reach for every week

TypeScript ships a standard library of type transformers. These are the five I use constantly and the one I keep forgetting.

Jan 8, 2026·5 min read

TypeScript's built-in utility types are underused. Not because developers do not know they exist — the docs cover them — but because they are listed alphabetically, and alphabetical order is a terrible way to learn things.

Here are the ones I reach for most, ordered by how often I need them.

#Partial<T> — all fields optional

When you want a version of a type where nothing is required:

type User = {
  id: string;
  name: string;
  email: string;
};
 
type UserUpdate = Partial<User>;
// { id?: string; name?: string; email?: string; }
 
async function updateUser(id: string, fields: Partial<Omit<User, 'id'>>) {
  return db.users.update(id, fields);
}

I use this almost exclusively for update/patch operations. Without the Omit, callers can pass id in the update body — which is rarely what you want.

#Pick<T, K> and Omit<T, K> — surgical selection

Pick keeps only the fields you name. Omit keeps everything except the fields you name. They are inverses.

type UserPreview = Pick<User, 'id' | 'name'>;
// { id: string; name: string; }
 
type UserWithoutId = Omit<User, 'id'>;
// { name: string; email: string; }

The rule I follow: use Pick when you want a small subset, use Omit when you want almost everything. If you are picking more fields than you are omitting, you have the wrong one.

#Record<K, V> — typed dictionaries

When you want an object with a specific key type and value type:

type OrderStatus = 'pending' | 'paid' | 'shipped' | 'cancelled';
type StatusLabel = Record<OrderStatus, string>;
 
const labels: StatusLabel = {
  pending: 'Awaiting payment',
  paid: 'Payment received',
  shipped: 'On its way',
  cancelled: 'Order cancelled',
};

The exhaustive constraint is the key benefit. If you add 'refunded' to OrderStatus, TypeScript will error on every Record<OrderStatus, ...> that does not handle it. The compiler forces you to update every callsite.

#ReturnType<T> and Parameters<T> — extract from functions

When you need the type of a function's return value or arguments without importing a separate type:

async function getUser(id: string) {
  return db.users.findById(id);
}
 
type User = Awaited<ReturnType<typeof getUser>>;
// Resolves the Promise and extracts the inner type
 
type GetUserArgs = Parameters<typeof getUser>;
// [id: string]

I use ReturnType constantly for functions that return complex objects. Instead of defining a User type separately and keeping it in sync with the function, I derive it. The function is the source of truth.

#NonNullable<T> — strip null and undefined

type MaybeUser = User | null | undefined;
type DefiniteUser = NonNullable<MaybeUser>;
// User

More useful in generic contexts:

function compact<T>(arr: (T | null | undefined)[]): NonNullable<T>[] {
  return arr.filter((x): x is NonNullable<T> => x != null);
}
 
const users = compact([getUser(1), null, getUser(2), undefined]);
// users: User[]

#The one I keep forgetting: Extract<T, U> and Exclude<T, U>

These filter union types. Extract keeps members assignable to U; Exclude removes them.

type Status = 'pending' | 'active' | 'inactive' | 'deleted';
 
type ActiveStatus = Extract<Status, 'active' | 'inactive'>;
// 'active' | 'inactive'
 
type VisibleStatus = Exclude<Status, 'deleted'>;
// 'pending' | 'active' | 'inactive'

The reason I forget them: I always try to use Pick/Omit on a union first, which does not work. Extract and Exclude are for unions; Pick and Omit are for object types.

If you are filtering members of a union type, you want Extract or Exclude. If you are filtering keys of an object type, you want Pick or Omit.


The utility types are a small library of building blocks. You do not need all of them — but knowing which half-dozen fit your patterns is the difference between types that compose cleanly and types you fight with.