Built-in utility types

TypeScript · Practical · 12 min

Utility types keep related types in sync from one source of truth instead of copy-pasting shapes.

TypeScript ships generic utility types that transform other types. Learning a dozen of them removes most hand-written duplication.

Reshaping objects:

  • Partial<T> — all properties optional (great for update/patch payloads).
  • Required<T> — remove all ?.
  • Readonly<T> — all properties readonly (shallow).
  • Pick<T, "a" | "b"> — keep only those keys.
  • Omit<T, "password"> — drop keys. Note Omit doesn't check that the keys exist; a typo silently omits nothing.
  • Record<K, V> — an object with keys K and values V, e.g. Record<"en" | "fr", string> requires both keys.

Working with unions:

  • Exclude<U, X> / Extract<U, X> — remove or keep union members assignable to X.
  • NonNullable<T> — remove null and undefined.

Working with functions and promises:

  • ReturnType<typeof fn> and Parameters<typeof fn> — the return type and parameter tuple of a function type. Note the typeof: utilities take types, not values.
  • Awaited<T> — the value a promise resolves to, unwrapping nested promises.

Strings: Uppercase, Lowercase, Capitalize, Uncapitalize transform string literal types.

A typical pattern: define one main type and derive the rest — type NewUser = Omit<User, "id" | "createdAt">, type UserPatch = Partial<NewUser>. When User changes, everything follows.

None of these are magic: they're written with mapped and conditional types in lib.es5.d.ts, which you'll learn to write yourself in the Advanced lessons. Hover over one in your editor to see its definition.

Example

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

type NewUser = Omit<User, "id" | "createdAt">;
type UserPatch = Partial<NewUser>;
type PublicUser = Pick<User, "id" | "name">;
type Staff = Exclude<User["role"], "guest">;            // "admin" | "member"
type Labels = Record<User["role"], string>;

async function loadUser(id: number): Promise<PublicUser> {
  return { id, name: "Ada" };
}
type Loaded = Awaited<ReturnType<typeof loadUser>>;     // PublicUser
type LoadArgs = Parameters<typeof loadUser>;            // [id: number]

const patch: UserPatch = { email: "ada@example.com" };
const labels: Labels = { admin: "Admin", member: "Member", guest: "Guest" };
const role: Staff = "member";
const args: LoadArgs = [1];
loadUser(...args).then((u: Loaded) => console.log(u.name, patch, labels[role]));

Remember

  • Derive types from one source with Pick, Omit, Partial, Record
  • Exclude/Extract/NonNullable filter unions
  • ReturnType<typeof f>, Parameters<typeof f>, Awaited<T> read types off functions and promises

Watch out for

  • Writing ReturnType<fn> instead of ReturnType<typeof fn> — utilities take types, not values.

Check yourself

  1. type T = Omit<{ a: string; b: number }, "c">. What happens?
  2. Which type requires an object to have exactly the keys "en" and "de" with string values?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: www.typescriptlang.org/docs/handbook/utility-types.html