Types built from types
Partial and Record: shapes derived from a shape you already have, so an update payload and a lookup table cannot drift from the thing they describe.
Do not write the shape twice
The second copy of a type is the one that goes stale. A settings screen has a full set of settings, and an update carries any subset of them — writing that subset by hand means editing two places every time a setting is added.
TypeScript can derive the second type from the first. Partial<Settings> is exactly "the same properties, every one of them optional" — a new setting is part of the update type the day it is part of the settings.
Two helpers worth knowing first
interface Settings {
theme: string;
volume: number;
language: string;
}
function apply(patch: Partial<Settings>): void {
console.log("applying", patch);
}
const labels: Record<string, string> = {
theme: "Theme",
volume: "Volume",
};Record is a table, spelled out
Record<string, string> is an object used as a lookup: any key, a text value. Record<Status, string> is the version with a menu — the keys can only be the statuses, and a typo in a key is caught like any other typo.
The pair covers most of what derived types are for: Partial<T> for patches, Record<K, V> for tables. There are others — Pick, Omit, Readonly — and they all follow the same rule: a new type written in terms of an existing one.
Why this matters in a real codebase
Derived types turn "remember to update both" into a fact the compiler enforces. Add a setting, and the record of labels stops compiling until it has the new key — which is when the reminder is cheapest.
That is the same bargain as everywhere in this course: write the relationship down once, and let the compiler keep it true. A derived type is not a shorter spelling; it is a statement that the two shapes are the same shape, and the compiler is the one that checks the statement.
Pick and Omit are one idea, two directions
Pick<Settings, "theme"> builds an object with only that property; Omit<Settings, "volume"> builds one with every property except that one. Both are computed from the original, so a change there reaches the derived type without an edit.
The choice is about which list stays short. Pick when you want a small, named subset; Omit when you want almost everything and one field is private. Neither copies a value — they are types, erased like the rest.
Readonly is shallow, and that is the point
Readonly<Settings> stops assignment to the top-level properties. It does not freeze a nested object: a profile field marked readonly can still have its own fields changed. The type describes the promise you made, not a deep lock.
When the object really must not change, build a new one instead. { ...settings, theme: "dark" } keeps the old value intact for anyone else holding it, and the spread is the idiom the rest of the type system expects.
How a derived type is computed
Partial<T> is not a special form the compiler recognises; it is a mapped type, written in the standard library as a loop over the keys of T. For each key it copies the property and adds a question mark. Pick and Omit work the same way, selecting from keyof T and rebuilding an object out of the chosen names.
That is why derived types stay in step. When Settings gains a key, keyof Settings gains it too, and every mapped type built on that list is recomputed. There is no second list to edit — the original shape is the only input, which is also why the derived type can never describe something the original does not.
What goes wrong
Partial<Settings> is shallow, and that surprises people at the second level: patch.profile may be missing, but if it is present its own required fields are still required. If a nested update must also be partial, say so: Partial<Settings> & { profile?: Partial<Profile> }.
The other trap is Record<string, T> used where the keys are a known menu. It accepts "them" as happily as "theme", so a typo becomes a new entry and the table silently grows a key nothing reads. Narrow the key to the union and the same typo is a compile error.
Three utility-type traps. Partial<T> is shallow: nested objects keep their required fields. Record<string, T> accepts any key, so a typo is a new entry rather than an error — narrow the key type when the keys are known. And Readonly<T> vanishes at run time; it stops your code, not a mutation from somewhere untyped.
Try these next
- Build a
PublicUserwithOmit<User, "email">and pass it to a function. - Make a
Readonly<Settings>and try to assign to one of its fields. - Compare
Pick<Settings, "theme" | "volume">with a hand-written shape.
A patch and a table
Type the update parameter as a partial set of the settings, and the labels as a record from text to text.
3 left to do
- The update is a partial set of settings
- The labels are a record
- The record maps text to text
Partial takes the interface in angle brackets: patch: Partial<Settings>. The record names both sides: labels: Record<string, string>.
A public view of a user
Derive a `PublicUser` type without the email, give `toPublic` that return type, and log the result.
3 left to do
- The public type omits the email
- The public type has a name
- The function returns the public type
type PublicUser = Omit<User, "email">; keeps every other field. The function returns that shape, built without the private field.
Prefer a derived type to a hand-written sibling, and prefer a union of literals to a bare string when the keys are a known menu — Record<Status, string> catches the typo that Record<string, string> waves through.
Check what you learned
One question at a time. A wrong answer explains itself — take another go.
Part 1 of 5
What does Partial<Settings> describe?
