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:
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" }));
{ 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:
interface Options { retries?: number; timeout?: number; } const defaults: Required<Options> = { retries: 3 };
error TS2741: Property 'timeout' is missing in type '{ retries: number; }' but required in type 'Required<Options>'.Readonly
Readonly<T> marks every property readonly:
interface Point { x: number; y: number; } const origin: Readonly<Point> = { x: 0, y: 0 }; origin.x = 5;
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:
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);
[ '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).
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:
type Locale = "en" | "fr" | "es"; const hello: Record<Locale, string> = { en: "Hello", fr: "Bonjour", es: "Hola", }; console.log(hello.fr);
Bonjour
Combining utility types
Utility types are ordinary generics, so they nest:
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);
{ 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
| Utility | Result |
|---|---|
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.