Utility Types

intermediate utility-types partial pick omit record

TS ships a bunch of built-in generic types that transform other types. Knowing these is non-negotiable — they show up in every real codebase and most interviews. The good news is they’re all just mapped + conditional types underneath, so once we know those, we know how each utility works.

Object-shape transforms

These take an object type and produce a tweaked version.

type User = { id: number; name: string; email: string };

type T1 = Partial<User>;   // { id?: number; name?: string; email?: string }
type T2 = Required<User>;  // strips ? off every property
type T3 = Readonly<User>;  // marks all properties readonly

Partial is the one we use most — perfect for PATCH endpoints and update functions where any subset of fields can be passed.

function updateUser(id: number, changes: Partial<User>) {
  // can pass { name: "new" } or { email: "x", name: "y" } or anything in between
}

Pick & Omit — slice and dice

Pick<T, K> keeps only the listed keys. Omit<T, K> drops them. These are the bread and butter for deriving smaller types from a master shape.

type User = { id: number; name: string; email: string; password: string };

type PublicUser = Omit<User, "password">;       // { id; name; email }
type Credentials = Pick<User, "email" | "password">; // { email; password }

In simple language: Pick is opt-in, Omit is opt-out. Reach for whichever is shorter to write — if we want most of the fields, use Omit; if we want a few, use Pick.

Record — build a uniform map

Record<K, V> builds an object type where every key in K maps to a value of type V. Think Map<K, V> but as a plain object type.

type Role = "admin" | "editor" | "viewer";
type Permissions = Record<Role, string[]>;
// { admin: string[]; editor: string[]; viewer: string[] }

const perms: Permissions = {
  admin: ["read", "write", "delete"],
  editor: ["read", "write"],
  viewer: ["read"],
}; // TS forces us to include all three roles

Union transforms — Exclude, Extract, NonNullable

These operate on union types, not object types. The names are confusing because they’re inverses of each other.

type Status = "loading" | "success" | "error" | null;

type Done = Exclude<Status, "loading">;     // "success" | "error" | null
type Finished = Extract<Status, "success" | "error">; // "success" | "error"
type Defined = NonNullable<Status>;          // "loading" | "success" | "error"

The only difference is direction: Exclude removes, Extract keeps. NonNullable is just Exclude<T, null | undefined> — a shortcut for the most common case.

These pull type info out of function and promise types using infer under the hood.

function createUser(name: string, age: number) {
  return { id: 1, name, age, createdAt: new Date() };
}

type User = ReturnType<typeof createUser>;
// { id: number; name: string; age: number; createdAt: Date }

type Args = Parameters<typeof createUser>;
// [name: string, age: number]

async function loadUser() { return { id: 1, name: "Ada" }; }
type LoadedUser = Awaited<ReturnType<typeof loadUser>>;
// { id: number; name: string } — Awaited unwraps the Promise

This is the killer combo: write a function once, derive its arg and return types as needed. No duplication.

Quick reference table

Utility Input Output
Partial<T>{ a: 1, b: 2 }{ a?: 1, b?: 2 }
Required<T>{ a?: 1 }{ a: 1 }
Readonly<T>{ a: 1 }{ readonly a: 1 }
Pick<T,K>{ a; b; c }, "a"{ a }
Omit<T,K>{ a; b; c }, "a"{ b; c }
Record<K,V>"a"|"b", number{ a: number; b: number }
Exclude<T,U>"a"|"b"|"c", "a""b" | "c"
Extract<T,U>"a"|"b"|"c", "a"|"d""a"
NonNullable<T>string | nullstring
ReturnType<F>() => UserUser
Parameters<F>(a: string) => void[a: string]
Awaited<T>Promise<User>User

Composing them

The real power is stacking utilities. A common pattern: build a “create” type that’s just the user-supplied fields of a record.

type DbUser = { id: number; name: string; email: string; createdAt: Date };

// Drop server-managed fields, then make everything required
type CreateUserInput = Required<Omit<DbUser, "id" | "createdAt">>;
// { name: string; email: string }

Compose these and we can model most real domain shapes without ever writing a duplicate type. That’s the entire point.