TSer/06 · Built-in utility types
CHAPTER 06 · Partial, Pick, Omit, Record

Built-in utility types

A utility type takes a type you already have and returns a new, related type. TypeScript ships a standard set of them, so you do not write the new type by hand. This chapter is about using them. Chapter 07 shows how they are built.

§01

Why utility types exist: one Order, five variants

The same tea shop. Order is the central type of the system, and real code needs several types that are almost, but not quite, Order.

Five receipts, five types

A draft order: the customer is still choosing, so properties may be missing. A locked order: the receipt is printed and nothing may change. A list row: three properties are shown. A public API response: the internal note must not leave the building. A stock table: one number per cup size. Five requirements, five types.

You could write out all five by hand from Order. Six properties, copied five times. Then Order gains a property, and five places have to be edited. Miss one and the types quietly disagree with each other.

order.ts · used throughout this chapter
1type Size = "small" | "medium" | "large";
2type Sugar = 0 | 30 | 50 | 70 | 100;
3
4interface Order {
5 id: string; // order number
6 drink: string; // what was ordered
7 size: Size; // cup size
8 sugar: Sugar; // sugar level, as a percentage
9 toppings: string[]; // extras
10 internalNote: string; // note for staff only
11}
This is the tea shop type from Chapter 01. §05 adds the order status union from Chapter 03. Paste this definition into the Playground and every later example in this chapter will compile against it.

TypeScript solves this with a set of utility types that ship with the language. Each one takes an existing type and produces a new one. The syntax looks like a function call, with angle brackets instead of parentheses: Partial<Order>. That is the same syntax as the generics in Chapter 05, and for the same reason: the type inside the brackets is an argument. One utility type covers each of the five requirements above.

Partial<Order>
Draft order: every property optional

The customer has not finished choosing, so any property may be left out. Six ? markers, added at once.

Readonly<Order>
Locked order: no property may be reassigned

After the receipt is printed, assigning to a property is a compile error. Six readonly markers, added at once.

Pick<Order, …>
List row: keep only the properties you name

A row shows the order number, the drink, and the cup size. An allow-list: named properties stay, the rest are left out.

Omit<Order, …>
Public API: remove the internal property

internalNote is for staff only. A block-list: named properties are removed, the rest stay as they are.

A utility type transforms; it never modifies

A utility type does not invent anything new. It reads an existing type and returns a new one. It also never changes its input: after Partial<Order> produces a draft type, Order still has the same six required properties. This holds for every utility type in the chapter, and the pipeline below shows it directly.

§02

Get the feel first: a type-processing line

Before the API list, watch the six properties of Order go through four different tools. Every later section is then a reminder of something you have already seen.

A type-processing line: one Order, a different type for each tool
type DraftOrder = Partial<Order>
In · Order
id: string
drink: string
size: Size
sugar: Sugar
toppings: string[]
internalNote: string
Partial<…>
Adds ? to every property. A draft order can be saved half-filled.
Out · DraftOrder
Waiting for output…
Six properties are queued on the left, and the machine is set to Partial. Press Next to send them through one at a time.
1 / 8

Three things to notice. A type goes in and a type comes out; nothing runs. Switching the tool changes only the output, never the input. And a property that Pick or Omit leaves out is missing from the new type only. It is still in Order, and any code that uses Order is unaffected.

§03

Change the shape: Partial, Required, Readonly

The first group adds and removes no properties. It changes how each existing property behaves: optional or required, writable or read-only.

Partial<T> · every property optional

A draft order can be saved half-filled
1type DraftOrder = Partial<Order>;
2// The same as writing this by hand:
3// {
4// id?: string;
5// drink?: string;
6// size?: Size;
7// sugar?: Sugar;
8// toppings?: string[];
9// internalNote?: string;
10// }
11
12const draft: DraftOrder = { drink: "Jasmine Milk Green" }; // ok
13const blank: DraftOrder = {}; // also ok
14
15draft.drink;
16// string | undefined, because the property may be absent
Chapter 02 added ? to a property by hand. Partial does that to every property at once. Read the last line carefully: because the property may be absent, reading it gives string | undefined, so you have to narrow it before use. Chapter 03 covers narrowing.

Required<T> · every property required

A confirmed order has to be complete
1type ConfirmedOrder = Required<DraftOrder>;
2// Every ? is removed, so the shape is Order again.
3
4const confirmed: ConfirmedOrder = { drink: "Grape Tea" };
5// Type '{ drink: string; }' is missing the following properties from
6// type 'Required<Partial<Order>>': id, size, sugar, toppings, internalNote
7
8// Required removes more than the question mark. It is written with -?,
9// and removing ? also removes undefined from the property type.
10type A = { note?: string };
11type B = Required<A>;
12// { note: string } — not { note: string | undefined }
13
14// This applies only to properties written with ?. A required property
15// whose type already includes undefined is left as it is.
16type C = { note: string | undefined };
17type D = Required<C>;
18// { note: string | undefined }
Partial and Required are opposites for the optional marker, but they are not exact inverses of each other. Partial adds ?, which adds undefined to the property type; Required removes both. That is why Required<A> above is { note: string } and not { note: string | undefined }.

Readonly<T> · every property read-only

A printed receipt must not change
1type LockedOrder = Readonly<Order>;
2
3const locked: LockedOrder = {
4 id: "A-102", drink: "Grape Tea", size: "large",
5 sugar: 50, toppings: ["boba"], internalNote: "less ice",
6};
7
8locked.size = "small";
9// Cannot assign to 'size' because it is a read-only property.
10
11locked.toppings.push("coconut jelly");
12// No error. The assignment to locked.toppings is blocked,
13// but the array that locked.toppings points at is not.
The last two lines are the point of this section. readonly rejects assignment to the property. It says nothing about the object or array the property points at, so push is still allowed.

This whole group is shallow

Partial, Required and Readonly only touch the properties of the top-level object. They never look inside a property type. In Partial<{ meta: { note: string } }>, meta becomes optional and meta.note stays required.

For Readonly this is worth stating twice, because the name suggests more than it does. It prevents assignment to the property. It does not freeze the object the property points at, and nothing about it exists at run time: Object.freeze is a separate, runtime thing.

There is no built-in deep version of any of these. All the parts you need are in Chapter 07, and DeepReadonly is one of the things you will write there.

§04

Choose properties: Pick, Omit, Record

The second group decides which properties exist: an allow-list, a block-list, and Record, which builds an object type from a set of keys and one value type.

Pick / Omit · allow-list and block-list

Pick: keep these
1// A list row shows three properties
2type OrderListItem = Pick<
3 Order,
4 "id" | "drink" | "size"
5>;
6// {
7// id: string;
8// drink: string;
9// size: Size;
10// }
Omit: remove these
1// A public API must not leak the note
2type PublicOrder = Omit<
3 Order,
4 "internalNote"
5>;
6// Every property of Order,
7// except internalNote

Which one to use? Whichever list is shorter. A list row needs three of six properties, so Pick is shorter. A public response removes one of six, so Omit is shorter. Both copy the optional and readonly markers of each property they keep.

Pick checks its keys. Omit does not.

Start with code that looks fine:

One misspelled key, two outcomes
1type Oops = Omit<Order, "internalNotes">;
2// One extra s, and no error at all.
3// Oops still has internalNote: a misspelled key removes nothing.
4
5type Safe = Pick<Order, "internalNotes">;
6// Type '"internalNotes"' does not satisfy the constraint 'keyof Order'.

The reason is in the two definitions. Pick<T, K> declares K extends keyof T, so K has to be a real key of T. Omit<T, K> declares K extends keyof any, which accepts any string, number or symbol. The wider constraint is deliberate: it lets you write Omit for a key that may or may not be present. The cost is that a typo is not an error.

This matters most in exactly the case where Omit is most tempting. If you remove a property because it must not be sent to a client, a misspelled key compiles and the property is still there. Use a Pick allow-list for that, so the compiler checks the names, or write a strict Omit of your own after Chapter 07.

Record<K, V> · build an object type from keys and a value type

A stock table and a menu lookup
1// Stock per cup size. The keys are the three members of Size.
2type CupStock = Record<Size, number>;
3
4const stock: CupStock = { small: 40, medium: 25, large: 0 };
5// Leave out large and you get:
6// Property 'large' is missing in type '{ small: number; medium: number; }'
7// but required in type 'CupStock'.
8
9// When the keys are not known in advance, use string.
10interface MenuItem { price: number; soldOut: boolean }
11type Menu = Record<string, MenuItem>;
12
13const menu: Menu = {
14 "Jasmine Milk Green": { price: 12, soldOut: false },
15 "Grape Tea": { price: 18, soldOut: true },
16};
17
18menu["Oolong Tea"];
19// MenuItem, not MenuItem | undefined — even though this key is missing
Record<Size, number> is stricter than { [k: string]: number } because Size is a finite union, so the compiler knows exactly which keys must exist. With string as the key type, Record becomes an index signature and there is nothing to check. Read the last line: an index signature promises a value for every key, so reading a missing one type-checks and returns undefined at run time. Turn on noUncheckedIndexedAccess (Chapter 10) to make the compiler add | undefined there.
§05

Filter unions: Exclude, Extract, NonNullable

The tools so far worked on object properties. This group works on the members of a union type, which brings back the order status from Chapter 03.

Three cuts of the same status union
1type OrderStatus = "queued" | "making" | "ready" | "done" | "cancelled";
2
3// The pickup screen only shows orders that are still in progress
4type ActiveStatus = Exclude<OrderStatus, "done" | "cancelled">;
5// "queued" | "making" | "ready"
6
7// The archive table only stores orders that are finished
8type ClosedStatus = Extract<OrderStatus, "done" | "cancelled">;
9// "done" | "cancelled"
10
11// A form may not have a sugar level yet. Clear the empty values first.
12type SugarInput = Sugar | null | undefined;
13type SugarValue = NonNullable<SugarInput>; // Sugar
One union, two filters: Exclude removes members, Extract keeps them
type ActiveStatus = Exclude<OrderStatus, "done" | "cancelled">
"queued"✓ kept"making"✓ kept"ready"✓ kept"done"✕ removed"cancelled"✕ removed
= "queued" | "making" | "ready"
Exclude removes the members that match the second argument. "done" and "cancelled" are dropped, and the rest stay. This is the list of orders that are still in progress, so it is the right type for the pickup screen.

Properties or members? Ask before you reach for a tool

Partial, Pick and Omit take an object type and work on its properties. Exclude, Extract and NonNullable take a union type and work on its members. When you are not sure which you need, ask: is this type a record of properties, or a list of alternatives?

All three of this group are applied to each member of the union separately, which is why Exclude<A | B | C, B> gives A | C instead of comparing the whole union at once. Chapter 07 shows the one-line definition that produces this behavior.

§06

From functions and promises: Parameters, ReturnType, Awaited

The third group reads a type out of a function type: what it takes, what it returns, and what an async result contains. This is most useful when you did not write the type yourself.

Reading types out of a function
1function makeOrder(drink: string, size: Size, sugar: Sugar): Order {
2 return {
3 id: crypto.randomUUID(), drink, size, sugar,
4 toppings: [], internalNote: "",
5 };
6}
7
8type MakeOrderArgs = Parameters<typeof makeOrder>;
9// [drink: string, size: Size, sugar: Sugar]
10// A tuple, with the parameter names kept as labels.
11
12type MadeOrder = ReturnType<typeof makeOrder>;
13// Order
14
15type Wrong = ReturnType<makeOrder>;
16// 'makeOrder' refers to a value, but is being used as a type here.
17// Did you mean 'typeof makeOrder'?
Look at typeof makeOrder. makeOrder is a value, and ReturnType needs a type. In a type position, typeof takes the type of a value, which is the bridge between the two. Leaving it out is the most common mistake here, and the last three lines show the exact error. This typeof shares its name with the JavaScript operator but does something different; Chapter 07 covers it.

When is this useful? When a library exports a function but not the type of its result, and when you do not want to give an internal function's result a name of its own. Note that Parameters<T> returns a tuple, not a union or an object, so MakeOrderArgs[1] is Size and the tuple can be spread straight into a call.

Awaited: what an async result contains
1declare function fetchOrder(id: string): Promise<Order>;
2// declare states the shape without writing an implementation.
3// Chapter 09 covers it; in the Playground it works fine.
4
5type FetchReturn = ReturnType<typeof fetchOrder>;
6// Promise<Order> — still wrapped
7
8type FetchedOrder = Awaited<FetchReturn>;
9// Order — unwrapped
10
11type Deep = Awaited<Promise<Promise<string>>>;
12// string — every layer is removed, like a chain of awaits
13
14type Plain = Awaited<string>;
15// string — a type that is not a Promise passes through unchanged
Awaited removes every layer of Promise, not just one, which matches what a chain of await does at run time. Over a union it is applied to each member, so Awaited<Promise<string> | number> is string | number.

Also in the set: four string types, and one rare one

Uppercase<"large"> is "LARGE" and Lowercase goes the other way. Capitalize<"size"> is "Size" and Uncapitalize goes the other way. All four work on string literal types only, and they become genuinely useful together with the template literal types in Chapter 07. For now, just recognize the names.

There is also NoInfer<T>, added in TypeScript 5.4. It marks one parameter position so the compiler does not use it when inferring a type argument. It is rare. Knowing it exists is enough.

§07

Compose them: a type out is a type in

Every utility type takes a type and returns a type, so the result of one can be the argument of the next. Most real project types are built this way.

Three real variants from the tea shop
1// Checkout page: only size and toppings may change, and both are optional
2type CheckoutPatch = Partial<Pick<Order, "size" | "toppings">>;
3// { size?: Size; toppings?: string[] }
4
5// Public order: remove the internal note, then make the rest read-only
6type PublicOrderView = Readonly<Omit<Order, "internalNote">>;
7
8// Status board: a count for each in-progress status
9type BoardStats = Record<Exclude<OrderStatus, "done" | "cancelled">, number>;
10// { queued: number; making: number; ready: number }
Read these from the inside out. Line 2 first picks two properties, then makes both optional. Line 9 first removes two members from the status union, then uses what is left as the key set of a Record.

Does the order matter? Sometimes. Two tools that work on the same level often commute, and two tools that work on different levels usually do not. Rather than memorising rules, check the result by hovering the alias:

When the order matters, and when it does not
1// Partial and Pick can be swapped. Both produce { size?: Size; toppings?: string[] }
2// because Pick copies the optional marker along with the property.
3type P1 = Partial<Pick<Order, "size" | "toppings">>;
4type P2 = Pick<Partial<Order>, "size" | "toppings">;
5
6// Here the order does matter, because the two tools work on different levels.
7type Board1 = Partial<Record<Size, MenuItem>>;
8// { small?: MenuItem; medium?: MenuItem; large?: MenuItem }
9// The keys are optional. Each value, if present, is a complete MenuItem.
10
11type Board2 = Record<Size, Partial<MenuItem>>;
12// { small: Partial<MenuItem>; medium: ...; large: ... }
13// All three keys are required. Each value may be an empty object.
14
15const b1: Board1 = { small: { price: 12, soldOut: false } }; // ok
16const b2: Board2 = { small: { price: 12, soldOut: false } };
17// Type '{ small: { price: number; soldOut: false; }; }' is missing the
18// following properties from type 'Board2': medium, large
Partial and Pick both act on the properties of the same object, so swapping them changes nothing. Partial<Record<…>> makes the keys optional, while Record<…, Partial<…>> keeps all keys required and makes each value incomplete. Those are two different types, and the last error shows it.

Three common mistakes

One: a utility type never changes its input. However deeply you nest them, Order is still Order. Each step produces a new type.

Two: Partial, Required and Readonly are shallow. Nested property types are untouched, and Readonly blocks assignment to the property rather than mutation of the object it points at. §03 covers this.

Three: a misspelled key in Omit is not an error. When you are removing a property for safety, check the spelling, or use a Pick allow-list so the compiler checks it for you.

Next chapter: how these are built

These tools are useful, and none of them is special. Partial is defined in one line of TypeScript, and Exclude is shorter. Chapter 07 takes them apart: mapped types, conditional types, keyof, infer. There are only a few parts, and by the end you can write every tool from this chapter yourself.

§08

Labs

Four tasks, all of which run in the TypeScript Playground: compose three Order variants, see Omit accept a misspelled key, compare Record with an index signature, and unwrap nested promises.

§09

Quiz

Nine questions. The two that are missed most often are about shallowness and about the misspelled key in Omit, and both were covered above.

QUESTION 01 / 9

What exactly does type DraftOrder = Partial<Order> do?

QUESTION 02 / 9

What happens with Omit<Order, "internalNotes">? Note the misspelled key: there is an extra s.

QUESTION 03 / 9

Given const o: Readonly<Order> = …, which line does the compiler reject?

QUESTION 04 / 9

Which of these take a union type and return a union with some members removed or kept? (Select all that apply.)

QUESTION 05 / 9

What is the main difference between Record<Size, number> and { [k: string]: number }?

QUESTION 06 / 9

In ReturnType<typeof makeOrder>, what is typeof doing?

QUESTION 07 / 9

Awaited<Promise<Promise<number>>> resolves to the type ____.

QUESTION 08 / 9

After type T = Partial<{ meta: { note: string } }>, which statement is true?

QUESTION 09 / 9

Given type A = { note?: string }, what is the type of note in Required<A>?

What to take away from this chapter
  • A utility type takes a type and returns a new type. The syntax looks like a function call with angle brackets, and the input type is never modified.
  • Change the shape: Partial adds ?, Required removes it along with undefined, and Readonly blocks assignment to each property. All three are shallow and only touch the top level.
  • Choose properties: Pick is an allow-list, Omit is a block-list, and Record<K, V> builds an object type and requires every key when K is a finite union. Only Pick checks its keys.
  • Filter unions: Exclude removes matching members, Extract keeps them, and NonNullable removes null and undefined. These work on members, not on properties.
  • From functions: Parameters gives a tuple and ReturnType gives the return type. Both need typeof fn, not fn. Awaited removes every layer of Promise.
  • They compose, and you read the result from the inside out. What is missing here (a deep Readonly, a strict Omit) you build yourself in Chapter 07.