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) orreadonly(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.
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.
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
Predict the modifier
In the second example, change
MyRequiredto use[P in keyof T]?:instead of-?:. Predict whatBbecomes, then check. Then try+readonlyinMyPartial(a plus adds a modifier, and is the same as writing it bare). - 2
Typo in Omit
Add
type Oops = MyOmit<User, "emial">;with a// ^?under it. Predict: error or not? It is not an error, andOopsstill has all three fields. Now tryMyPick<User, "emial">and read the error. - 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 tryStrictOmit<{ a: 1; x: 1 } | { a: 2; y: 2 }, "x">: it is an error, becausekeyofof a union lists only the keys every member has (here just"a"), althoughxdoes exist in one member. The built-inOmitaccepts such a key, which is one reason its constraint is loose.
Code & diagrams
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 }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.
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);.
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 iteratekeyof Tof a type parameter, the checker keeps each property's existing?andreadonlymodifiers 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 }>givesa?: number, which accepts a missingabut not an explicita: undefined. If your patches may carryundefined, write{ [K in keyof T]?: T[K] | undefined }. - ▸
OmitandPickare not distributive:Omit<A | B, "x">first takeskeyof (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 tricktype Pretty<T> = { [K in keyof T]: T[K] } & {}to make the editor expand it.
Remember this
- 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 typeUser, but with every property optional". It lives in the TypeScript library files, so it is always available with no import. - 2
Nearly all object utilities are mapped types (Topic 8.4):
{ [P in keyof T]: ... }walks over every keyPofTand writes one property per key. A modifier is the small mark on a property:?makes it optional andreadonlyblocks assignment. A mapped type can add a modifier (?,readonly) or remove it with a minus sign (-?,-readonly). - 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>addsreadonlyto every property so the checker refuses assignments to them. - 4
Pick<T, K>keeps only the keys named inK.Omit<T, K>keeps everything except the keys inK. They are opposites, but they are not built the same way:Pickis a mapped type overK, whileOmitisPick<T, Exclude<keyof T, K>>(it works out the remaining keys first, usingExcludefrom Topic 10.2). One surprise:Omitaccepts any key name, even one that doesn't exist, so a typo likeOmit<Shape, "sizee">is not an error and omits nothing.Pickis stricter becauseK extends keyof T. - 5
Record<K, V>builds an object type whose keys areKand whose values are allV. 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 likestring, it is just an index signature, and withnoUncheckedIndexedAccess(on in this course) reading a key givesV | undefined. - 6
Two limits to remember.
ReadonlyandPartialare shallow: they change the top-level properties only, soReadonly<{ tags: string[] }>still lets you calltags.push(...). Topic 10.3 builds deep versions. And all of this exists only in the checker: at runtimeObject.freezeis what actually blocks a write, TypeScript only warns you.
Explain it without notes
How is Partial<T> defined, and what does each part mean?
What is the difference between Pick and Omit, and why does Omit not catch a mistyped key?
Why is Readonly<T> called shallow? Show an example.
When would you use Record<K, V> instead of an interface?
Practice
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.
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 likePartial<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,PickandRecordfrom memory as mapped types.I can explain how
Omitis built fromPickandExclude.I can say why
Omitaccepts a mistyped key andPickdoes not.I can show that
Readonlyis shallow with a real example.I can use
-?and-readonlyto remove a modifier.