Utility Types

TypeScript ships with a set of generic "utility types" that transform an existing type into a new one — making every property optional, picking a subset of properties, freezing an object, and so on. They save you from writing near-duplicate interfaces by hand.

Partial

Partial<T> makes every property of T optional. It's the natural type for an "update" or "patch" object:

TS partial.ts
interface Profile {
  name: string;
  email: string;
  bio: string;
}

function update(current: Profile, changes: Partial<Profile>): Profile {
  return { ...current, ...changes };
}

const me: Profile = { name: "Kim", email: "kim@x.io", bio: "" };
console.log(update(me, { bio: "Writes TypeScript" }));
Output
{ name: 'Kim', email: 'kim@x.io', bio: 'Writes TypeScript' }

Without Partial, you'd need a second interface with every property marked ? — and remember to update both whenever Profile changes.

Required

Required<T> is the opposite: it removes every ?. It's useful after defaults have been filled in:

TS required.ts
interface Options {
  retries?: number;
  timeout?: number;
}

const defaults: Required<Options> = { retries: 3 };
tsc output
error TS2741: Property 'timeout' is missing in type '{ retries: number; }' but required in type 'Required<Options>'.

Readonly

Readonly<T> marks every property readonly:

TS readonly.ts
interface Point {
  x: number;
  y: number;
}

const origin: Readonly<Point> = { x: 0, y: 0 };
origin.x = 5;
tsc output
error TS2540: Cannot assign to 'x' because it is a read-only property.

It's shallow: if a property holds an object, that inner object's properties can still be changed.

Pick and Omit

Pick<T, Keys> keeps only the listed properties; Omit<T, Keys> keeps everything except them:

TS pick-omit.ts
interface User {
  id: number;
  name: string;
  email: string;
  passwordHash: string;
}

type PublicUser = Omit<User, "passwordHash">;
type UserPreview = Pick<User, "id" | "name">;

function toPublic({ passwordHash, ...rest }: User): PublicUser {
  return rest;
}

const preview: UserPreview = { id: 1, name: "Rae" };
const u = toPublic({ id: 1, name: "Rae", email: "r@x.io", passwordHash: "…" });
console.log(Object.keys(u), preview);
Output
[ 'id', 'name', 'email' ] { id: 1, name: 'Rae' }

A typical use is API layers: a database type with every column, and Omit-derived types for what's safe to send to the browser or what a "create" form submits (often Omit<User, "id">, since the database assigns the ID).

Note: Pick only accepts keys that exist on the type — a typo is a compile error. Omit accepts any string, so a misspelt key silently omits nothing. Double-check Omit key names.

Record

Record<Keys, Value>, introduced in lesson 25, builds an object type from a set of keys and a value type:

TS record.ts
type Locale = "en" | "fr" | "es";

const hello: Record<Locale, string> = {
  en: "Hello",
  fr: "Bonjour",
  es: "Hola",
};

console.log(hello.fr);
Output
Bonjour

Combining utility types

Utility types are ordinary generics, so they nest:

TS combine.ts
interface Article {
  id: number;
  title: string;
  body: string;
  tags: string[];
  publishedAt: Date | null;
}

type ArticleDraft = Partial<Omit<Article, "id" | "publishedAt">>;
type ArticleCard = Readonly<Pick<Article, "id" | "title">>;

const draft: ArticleDraft = { title: "Untitled" };
const card: ArticleCard = { id: 9, title: "Hello" };
console.log(draft, card);
Output
{ title: 'Untitled' } { id: 9, title: 'Hello' }

Read nested utilities from the inside out: start with Article, omit two properties, then make what's left optional.

Quick reference

UtilityResult
Partial<T>All properties optional
Required<T>All properties required
Readonly<T>All properties readonly
Pick<T, K>Only properties K
Omit<T, K>All properties except K
Record<K, V>Object with keys K, values V

The next lesson covers utilities that work on unions and functions — Exclude, Extract, NonNullable, ReturnType, Parameters and Awaited. Lesson 28 then shows how all of these are built, so you can write your own.

Try it yourself

Try it yourself
Console output