Lesson 9 · Module 1.4 — Core type vocabulary

The everyday utility types — Partial, Record, Pick, Omit, Exclude, Extract

Real-world code leans on these built-ins constantly — Partial<Record<Exclude<…>, string>> shows up verbatim — yet they’re rarely spelled out. Each is a small type-level function: it takes a type and returns a transformed one. Learn the six everyday ones and you can read most library signatures.

The catalog — read each as “type in, type out”

utilityreads asexample → result
Partial<T>every property optionalPartial<{a:1}> → {a?:1}
Record<K,V>object with keys K, values VRecord<string,number>
Pick<T,K>keep only keys K of TPick<User,"id">
Omit<T,K>drop keys K from TOmit<User,"password">
Exclude<U,X>remove members X from union UExclude<"a"|"b","b"> → "a"
Extract<U,X>keep members of U assignable to XExtract<"a"|1,string> → "a"

Exclude and Extract are a complementary pair, and the thing to notice is that their second argument is a type used as a filter — read it “…members assignable to X,” not “the member literally named X.” Extract<U, X> keeps the members of U that match X; Exclude<U, X> drops them:

Extract<"a" | 1 | "b", string>   // "a" | "b"   — keeps the members that ARE strings
Exclude<"a" | 1 | "b", string>   // 1           — drops those, keeps the rest

So in Extract<keyof T, string> the string is a category, not a key name. keyof T can contain number and symbol keys as well as string ones, and Extract<…, string> narrows it to just the string-named keys — the form many mapped and template-literal types need, since those require a key that extends string.

A classic trap: Omit vs Exclude

They sound interchangeable but operate on different things — and reading them confused is a common slip:

Omit<T, K>Exclude<U, X>
operates onan object type Ta union U
removesthe properties named in Kthe union members in X
returnsa smaller object typea smaller union
type User = { id: number; name: string; password: string };

type Public  = Omit<User, "password">;        // { id: number; name: string }   — drops a PROPERTY
type Visible = Exclude<keyof User, "password">; // "id" | "name"                  — drops a UNION member

So Omit works on the object; Exclude works on the union of its keys. In fact Omit<T, K> is built on top of Exclude — it exists exactly so you don’t have to hand-roll Pick<T, Exclude<keyof T, K>>.

Composing them — a real-world example

A well-known i18n type nests three at once. Read it inside-out:

Partial<Record<Exclude<PluralRule, "other">, string>>
//      	└─ Exclude: drop "other" from the union of plural rules
//   	└──── Record: build an object keyed by those remaining rules, values string
// Partial: ...and make every one of those keys optional

Three small functions chained: filter a union, build an object from it, loosen it. That is the whole pattern behind most “scary” utility expressions.

The recursive idea: DeepPartial

A common gotcha: Partial<T> only loosens the top level — nested objects stay required. DeepPartial recurses to fix that:

type DeepPartial<T> = {
  [P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P];
};

Read it as: “for every key, make it optional (?); if its value is an object, apply DeepPartial again, otherwise keep the value type.” The recursion — DeepPartial calling itself on T[P] — is what reaches all the way down. You won’t often write this, but you’ll read it (and its array-handling cousins) in real codebases.

Don’t confuse the pair

Omit takes an object and removes properties; Exclude takes a union and removes members. If the first argument is { … } it’s Omit; if it’s A | B | C it’s Exclude.

Check yourself

Recall, don’t re-read. (Answers reveal on click.)

The difference between Omit and Exclude is that Omit
Partial<User> where User has a nested address object will…
How do you read Record<string, number>?

Primary source

The canonical list is the handbook’s Utility Types page — it gives the one-line definition of every one of these. Best way to feel it: paste Omit<User,"password"> and Exclude<keyof User,"password"> into the TS Playground and hover both results side by side.

Try it yourself

Met a nested utility expression you can’t unwind? Read it inside-out, one utility at a time. Next lesson (1.5): tuples vs arrays — why a function’s parameter list is a tuple.