Command Palette

Search for a command to run...

Hectal
PHASE 10Advanced ~21 min· topic 1 of 6

Topic 10.1

Object Utility Types

In one line

Partial, Required, Readonly, Pick, Omit and Record make a new object type from an old one. Each is a one-line mapped type, and writing them yourself shows exactly what they do.

Think of it like this

A school form with boxes for name, email and phone. A photocopier with settings can make a copy where every box is optional (a draft), a copy where every box must be filled, a copy locked in a frame so nobody can write on it, or a copy with only two of the boxes. You never redraw the form by hand: you change the original, and every copy follows. Utility types are those photocopier settings for types.

Words you'll meet

New words in this topic, in plain English. Come back here whenever one feels fuzzy.

Utility type
A ready-made generic type from the TypeScript library that turns one type into another, like Partial<User>.
Mapped type
A type that walks over every key of another type and writes one property for each: { [P in keyof T]: ... }.
Modifier
A mark on a property: ? (optional) or readonly (cannot be assigned to).
`-?` and `-readonly`
A minus in front of a modifier removes it, so -? makes an optional property required again.
`keyof T`
The union of all the key names of T, for example "id" | "name".
Shallow
Only the top level is changed. Objects nested inside keep their old properties.
Index signature
A rule like { [key: string]: number } that says any text key holds a number.
Patch
An object holding only the fields you want to change, a good use for Partial.

Step by step

01Start from one interface

Every utility below starts from the same User. It has a normal field, an optional one (email?) and a readonly one.

A utility never changes User. It makes a new type from it, so User stays exactly as written.

user.tswhole filets
interface User {
  id: number;
  name: string;
  email?: string;
  readonly createdAt: number;
}

02Read the real definition

These are the exact lines from TypeScript 6.0's lib.es5.d.ts. Read them slowly: each is only a mapped type.

[P in keyof T] means "for every key P of T". T[P] is the type of that property. ?, -? and readonly are the modifiers.

lib.es5.d.tswhole filets
type Partial<T> = {
    [P in keyof T]?: T[P];
};

type Required<T> = {
    [P in keyof T]-?: T[P];
};

type Readonly<T> = {
    readonly [P in keyof T]: T[P];
};

type Pick<T, K extends keyof T> = {
    [P in K]: T[P];
};

type Record<K extends keyof any, T> = {
    [P in K]: T;
};

type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;

03Use them and look at the results

Ask // ^? for each result. Partial makes everything optional, including the readonly field (which stays readonly). Required removes the ? from email.

Pick<User, "id" | "name"> keeps two fields. Omit<User, "id"> keeps the other three and still remembers that createdAt is readonly.

04Build them yourself

Copy each definition under a new name and compare the results. They match exactly, because there is no secret: the built-ins are ordinary TypeScript.

This is the best way to learn the modifiers: change ? to -? in your copy and watch the panel.

05Know the shallow edge

Readonly<Settings> blocks s.theme = "light" but not s.tags.push("b"), because tags is an array and the array's own methods are untouched. The first line is an error, the second runs and changes the data.

When you need deep protection you write a recursive version (Topic 10.3) or use as const (Topic 4.2).

Try it yourself

  1. 1

    Predict the modifier

    In the second example, change MyRequired to use [P in keyof T]?: instead of -?:. Predict what B becomes, then check. Then try +readonly in MyPartial (a plus adds a modifier, and is the same as writing it bare).

  2. 2

    Typo in Omit

    Add type Oops = MyOmit<User, "emial">; with a // ^? under it. Predict: error or not? It is not an error, and Oops still has all three fields. Now try MyPick<User, "emial"> and read the error.

  3. 3

    A stricter Omit

    Write type StrictOmit<T, K extends keyof T> = MyPick<T, Exclude<keyof T, K>>; and use it with a misspelt key. The typo is now an error. Now try StrictOmit<{ a: 1; x: 1 } | { a: 2; y: 2 }, "x">: it is an error, because keyof of a union lists only the keys every member has (here just "a"), although x does exist in one member. The built-in Omit accepts such a key, which is one reason its constraint is loose.

Code & diagrams

The six object utilities on one interface

Each query shows the type the utility produced. Notice createdAt stays readonly inside Draft and NoId.

Console

(nothing was printed)

Expected console output

{ name: 'Asha' } { admin: true, guest: false }
Build all six yourself

Same results as the built-ins. The error at the end is the readonly modifier doing its job: the code still runs, because types are erased.

Console

(nothing was printed)

Expected console output

{ a: 1, b: 2 }

Break it on purpose

Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.

Break #1

Assign to a Readonly property

Run the second example without changing anything and read the last error. Then remove readonly from MyReadonly.

terminal
$ Types panel
── what you'll see ──
index.ts(28,5): error TS2540: Cannot assign to 'id' because it is a read-only property.

Break #2

Readonly is only one level deep

In a new file write these five lines: interface Settings { theme: string; tags: string[] }, then const s: Readonly<Settings> = { theme: "dark", tags: ["a"] };, then s.theme = "light";, then s.tags.push("b");, then console.log(s);.

terminal
$ Types panel
── what you'll see ──
index.ts(3,3): error TS2540: Cannot assign to 'theme' because it is a read-only property.

Myth vs fact

Myth

Omit<T, K> checks that K is a real key of T.

Fact

It doesn't: K extends keyof any, so any text is accepted and a typo silently omits nothing. Pick<T, K> does check. Write a StrictOmit if you want the check.

Myth

Readonly<T> makes an object immutable.

Fact

It changes the type only, and only one level deep. At runtime the object is untouched; Object.freeze is what blocks writes, and it is shallow too.

Myth

Utility types make a copy of the data.

Fact

They exist only at compile time and produce a type. No JavaScript is generated for them.

Pro corner

Extra depth for experienced readers. New to this? Skip it for now and come back later.

  • ▸

    Partial<T> and friends are homomorphic mapped types: because they iterate keyof T of a type parameter, the checker keeps each property's existing ? and readonly modifiers and (for arrays and tuples) maps element by element. Record<K, V> is not homomorphic, which is why it has no modifiers to preserve.

  • ▸

    With exactOptionalPropertyTypes (on in this course), Partial<{ a: number }> gives a?: number, which accepts a missing a but not an explicit a: undefined. If your patches may carry undefined, write { [K in keyof T]?: T[K] | undefined }.

  • ▸

    Omit and Pick are not distributive: Omit<A | B, "x"> first takes keyof (A | B) (only the shared keys), so the union collapses into one object type. For { a: 1; x: 1 } | { a: 2; y: 2 } the result is { a: 1 | 2 }. To omit from each member separately, write a distributive version: type DistOmit<T, K extends PropertyKey> = T extends unknown ? Omit<T, K> : never;.

  • ▸

    Hover text for a utility result can be unreadable (Pick<Omit<...>>). Wrap it in a named alias, or use the identity trick type Pretty<T> = { [K in keyof T]: T[K] } & {} to make the editor expand it.

Remember this

  1. 1

    The problem this solves: Variations of one shape, such as a draft with every field optional, would otherwise be retyped by hand and drift out of step with the original. A utility type is a generic type, already written for you, that takes a type in and gives a changed type out. You use it like a function: Partial<User> means "the type User, but with every property optional". It lives in the TypeScript library files, so it is always available with no import.

  2. 2

    Nearly all object utilities are mapped types (Topic 8.4): { [P in keyof T]: ... } walks over every key P of T and writes one property per key. A modifier is the small mark on a property: ? makes it optional and readonly blocks assignment. A mapped type can add a modifier (?, readonly) or remove it with a minus sign (-?, -readonly).

  3. 3

    Partial<T> adds ? to every property, so you can build the object in pieces (a form draft, a patch to send to a server). Required<T> does the reverse with -?, so every property must exist. Readonly<T> adds readonly to every property so the checker refuses assignments to them.

  4. 4

    Pick<T, K> keeps only the keys named in K. Omit<T, K> keeps everything except the keys in K. They are opposites, but they are not built the same way: Pick is a mapped type over K, while Omit is Pick<T, Exclude<keyof T, K>> (it works out the remaining keys first, using Exclude from Topic 10.2). One surprise: Omit accepts any key name, even one that doesn't exist, so a typo like Omit<Shape, "sizee"> is not an error and omits nothing. Pick is stricter because K extends keyof T.

  5. 5

    Record<K, V> builds an object type whose keys are K and whose values are all V. With a union of exact texts, Record<"admin" | "guest", boolean> demands both keys, which makes it a favourite for lookup tables where nothing may be forgotten (Topic 10.5). With a wide key like string, it is just an index signature, and with noUncheckedIndexedAccess (on in this course) reading a key gives V | undefined.

  6. 6

    Two limits to remember. Readonly and Partial are shallow: they change the top-level properties only, so Readonly<{ tags: string[] }> still lets you call tags.push(...). Topic 10.3 builds deep versions. And all of this exists only in the checker: at runtime Object.freeze is what actually blocks a write, TypeScript only warns you.

Explain it without notes

01

How is Partial<T> defined, and what does each part mean?

02

What is the difference between Pick and Omit, and why does Omit not catch a mistyped key?

03

Why is Readonly<T> called shallow? Show an example.

04

When would you use Record<K, V> instead of an interface?

Practice

01

Given interface Product { id: number; title: string; price: number; tags: string[] }, write a type ProductPatch where id is required but every other field is optional, using only built-in utilities.

02

Write your own MyReadonly<T> and Mutable<T> (the reverse, removing readonly) and show with // ^? that Mutable<Readonly<Product>> gives back the original.

Trade-offs

  • ↔

    Deriving types (Omit<User, "id">) keeps them in sync with the source, but long chains like Partial<Omit<Pick<...>>> are hard to read in hovers and errors. Give important results a name.

  • ↔

    Built-ins are shallow and a little loose (Omit). That is fine for most code; reach for your own version only when the looseness has bitten you.

Done when you can

  • I can write Partial, Required, Readonly, Pick and Record from memory as mapped types.

  • I can explain how Omit is built from Pick and Exclude.

  • I can say why Omit accepts a mistyped key and Pick does not.

  • I can show that Readonly is shallow with a real example.

  • I can use -? and -readonly to remove a modifier.