Made by VeraStack Labs
Contents
One form, many versions
Edit form, public card, price list: same data, different views.
Think about a signup form for a small app. It has four boxes: id, name, email and password. That is one shape, and you wrote it once as a type in lesson 3.
Now the app needs more forms from the same data.
The edit form lets a user change one box at a time. So every box becomes optional. The public profile, which other people see, must hide the password. The price list has one price for every drink on the menu, and not one drink may be forgotten.
You could write a new type for each. Four boxes, copied and trimmed, three times. Then someone adds a "phone" box to the first type, and the other three go stale. This is the copy-and-paste trap.
TypeScript gives you a better way. It ships with a toolbox of utility types: small, ready-made helpers that take a type you already have and give back a reshaped one. "Same as User, but every box optional." "Same as User, but without the password." You write the shape once, and the others follow it.
Remember: a utility type is a built-in helper that makes a new type out of an old one, so you never copy a type by hand.
Copies that drift
JavaScript has no shape to reshape.
In JavaScript, an update function takes the user and a bag of changes. Nothing says which changes are allowed.
function updateUser(user, changes) { return { ...user, ...changes };}const asha = { id: 1, name: "Asha", email: "asha@example.com", password: "chai123" };const renamed = updateUser(asha, { nmae: "Asha Rao" });console.log(renamed.name);AshaWe typed nmae, not name. The function happily added a new property called nmae. The name didn't change, and nobody was told.
Hiding a password is worse. A function called toPublic should strip it out. It's easy to write one that doesn't.
function toPublic(user) { return user;}const asha = { id: 1, name: "Asha", email: "asha@example.com", password: "chai123" };console.log(toPublic(asha));{ id: 1, name: 'Asha', email: 'asha@example.com', password: 'chai123' }The "public" card shows the password. JavaScript has nothing that says "this object must not have that".
And a price list with a missing drink is silent too.
const prices = { chai: 10, coffee: 20,};console.log(prices.lassi);undefinedRemember: in JavaScript, nothing connects the edit form, the public card and the price list to the original shape.
Reshape, don't retype
Partial, Omit and Record catch the slips above.
Start with the edit form. Partial<User> means "a User, but every property is optional". The angle brackets are the generic syntax from lesson 7: a utility type is a generic type, and you pass it your type.
function updateUser(user, changes) { return { ...user, ...changes };}const asha = { id: 1, name: "Asha", email: "asha@example.com", password: "chai123" };const renamed = updateUser(asha, { nmae: "Asha Rao" });console.log(renamed.name);Tinted lines are the ones that differ
The misspelled nmae is refused, and the error even mentions Partial<User>. A correct edit goes through.
type User = { id: number; name: string; email: string; password: string };function updateUser(user: User, changes: Partial<User>): User { return { ...user, ...changes };}const asha: User = { id: 1, name: "Asha", email: "asha@example.com", password: "chai123" };const renamed = updateUser(asha, { name: "Asha Rao" });console.log(renamed.name);console.log(renamed.email);Asha Rao
asha@example.comNow the public card. Omit<User, "password"> means "a User, without the password".
function toPublic(user) { return user;}const asha = { id: 1, name: "Asha", email: "asha@example.com", password: "chai123" };console.log(toPublic(asha));Tinted lines are the ones that differ
Now PublicProfile has no password property, so any code that reads one off a card is refused. That protects the readers. The writer still has to build the card properly, by really removing the password. Here is a version that does.
type User = { id: number; name: string; email: string; password: string };type PublicProfile = Omit<User, "password">;function toPublic(user: User): PublicProfile { const { password, ...rest } = user; return rest;}const asha: User = { id: 1, name: "Asha", email: "asha@example.com", password: "chai123" };console.log(toPublic(asha));{ id: 1, name: 'Asha', email: 'asha@example.com' }The const { password, ...rest } = user line pulls the password out and leaves the rest. This is plain JavaScript, called destructuring with a rest, and TypeScript types the rest correctly.
Finally the price list. Record<Drink, number> means "an object with one number for every drink".
const prices = { chai: 10, coffee: 20,};console.log(prices.lassi);Tinted lines are the ones that differ
Forget the lassi, and the checker names it. Add a fourth drink to the Drink type next year, and this price list turns red until you give it a price. That is the same safety as the never check in lesson 5.
Remember: Partial makes everything optional, Omit takes properties away, and Record makes you fill in every key.
The whole toolbox
Seven helpers, each in one example.
Here is the set you will use most. Each takes the shape you have and gives back a new one.
| Utility | What it gives you |
|---|---|
Partial<T> | every property optional |
Required<T> | every property required |
Pick<T, "a" | "b"> | only the named properties |
Omit<T, "a"> | everything except the named properties |
Record<K, V> | an object with every key K, each holding a V |
Readonly<T> | every property readonly |
ReturnType<typeof f> | whatever the function f gives back |
Required is the opposite of Partial. It is useful when you fill in defaults, and want a type that promises nothing is missing afterwards.
type Settings = { theme?: string; fontSize?: number };function withDefaults(settings: Settings): Required<Settings> { return { theme: settings.theme ?? "dark", fontSize: settings.fontSize ?? 14 };}console.log(withDefaults({}));console.log(withDefaults({ fontSize: 18 }));{ theme: 'dark', fontSize: 14 }
{ theme: 'dark', fontSize: 18 }Pick keeps only what you name. It is good for a small preview of a big type.
type User = { id: number; name: string; email: string; password: string };type Preview = Pick<User, "id" | "name">;const preview: Preview = { id: 1, name: "Asha" };console.log(preview);{ id: 1, name: 'Asha' }Misspell a key in Pick, and you get an error, because Pick only accepts keys that exist.
type User = { id: number; name: string; email: string };type Preview = Pick<User, "id" | "nmae">;TypeScript says
error TS2344: Type '"id" | "nmae"' does not satisfy the constraint 'keyof User'. Type '"nmae"' is not assignable to type 'keyof User'.
Readonly is lesson 3's readonly, applied to every property at once.
const asha = { id: 1, name: "Asha" };asha.name = "Asha Rao";console.log(asha.name);Tinted lines are the ones that differ
ReturnType borrows the result type of a function, so you never write it twice. It uses the word typeof in a type position, which means "the type of this thing". It's different from the typeof in lesson 5, which asked a question at runtime. This one is only for types.
function makeOrder(table: number) { return { table, items: [] as string[], paid: false };}type OrderTicket = ReturnType<typeof makeOrder>;const ticket: OrderTicket = makeOrder(4);console.log(ticket);{ table: 4, items: [], paid: false }If someone adds a field to makeOrder later, OrderTicket follows. No second place to edit.
Combine them. Utility types stack, as you'd stack boxes. Here is "an edit form for only the name and email".
type User = { id: number; name: string; email: string; password: string };type ProfileEdit = Partial<Pick<User, "name" | "email">>;const edit: ProfileEdit = { email: "new@example.com" };console.log(edit);{ email: 'new@example.com' }Read it from the inside out: Pick the two properties, then make them Partial.
How do they work? There is no magic. Partial is a mapped type, a type that loops over the properties of another type and changes each one. This is the whole of it.
type User = { id: number; name: string };// This is all Partial is: every property, made optional.type MyPartial<T> = { [K in keyof T]?: T[K] };const changes: MyPartial<User> = { name: "Asha Rao" };[K in keyof T] means "for each property name K of T", and the ? makes it optional. You will rarely write one, but it is good to know the helpers are only ordinary types.
Remember: Pick and Omit are opposites, Partial and Required are opposites, and they stack from the inside out.
Four ways to get fooled
Silent Omit, shallow Readonly, and friends.
A typo in Omit is not an error. This surprises everyone once. Pick checks its keys, but Omit doesn't.
type User = { id: number; name: string; password: string };// A typo in Omit is not an error. Nothing is removed.type Oops = Omit<User, "pasword">;Oops still has the password. The wrong word removed nothing, and nothing complained. If a missing property would be a security slip, double check the spelling, or use Pick with the safe fields instead, because Pick lists what is allowed, and a typo there is caught.
Readonly is shallow. It makes the top-level properties read-only. An object or list inside can still be changed.
Partial loosens everything. A Partial<User> also allows {}, and also allows { name: undefined }. If one field must always be there, build the type with Pick and Partial together, and keep that field required.
A Record with plain string keys trusts you. Record<string, number> says any text is a key. Reading a key that isn't there gives undefined at runtime, but the type still says number. Use a list of exact words, as in Record<Drink, number>, when you can.
Remember: Omit doesn't check its keys, Readonly is shallow, and Partial allows too much for fields that must exist.
Three forms, reshaped
An update, a public card, and a price table.
Each problem starts with a JavaScript function that takes anything. Add types, and use a utility type.
Problem 1
Change only what's given
Type patch with a Profile type and Partial<Profile> for the changes. The call with the typo cty should be refused. Remove it.
Start here
function patch(profile, changes) { return { ...profile, ...changes };}console.log(patch({ name: "Asha", city: "Pune", phone: "98450" }, { city: "Mumbai" }));console.log(patch({ name: "Asha", city: "Pune", phone: "98450" }, { cty: "Mumbai" })); // a typo, quietly addedStuck?
Show a solution
type Profile = { name: string; city: string; phone: string };function patch(profile: Profile, changes: Partial<Profile>): Profile { return { ...profile, ...changes };}patch({ name: "Asha", city: "Pune", phone: "98450" }, { city: "Mumbai" });Partial<Profile> allows any subset of the profile's properties, and nothing else. The spread copies the old profile and then lays the changes over it. The typo cty is refused instead of being quietly added as a new property.
Problem 2
A card without secrets
Type publicCard, so it returns an account without the phone and the password, and really leaves them out.
Start here
function publicCard(account) { return account;}console.log(publicCard({ id: 1, name: "Asha", phone: "98450", password: "chai123" })); // leaks the phone and passwordStuck?
Show a solution
type Account = { id: number; name: string; phone: string; password: string };function publicCard(account: Account): Omit<Account, "phone" | "password"> { return { id: account.id, name: account.name };}publicCard({ id: 1, name: "Asha", phone: "98450", password: "chai123" }); // { id: 1, name: "Asha" }Omit<Account, 'phone' | 'password'> is the type of a card with only the safe fields. The function builds a new object with just those two, so the phone and password are not carried along. The JavaScript version returned the whole account, secrets included.
Problem 3
Prices for every size
Type the price table with Record, and priceOf with the Size union. The call with "huge" should be refused. Remove it.
Start here
const priceBySize = { small: 10, medium: 15, large: 20,};function priceOf(size) { return priceBySize[size];}console.log(priceOf("large")); // 20console.log(priceOf("huge")); // undefinedStuck?
Show a solution
type Size = "small" | "medium" | "large";const priceBySize: Record<Size, number> = { small: 10, medium: 15, large: 20,};function priceOf(size: Size): number { return priceBySize[size];}priceOf("large"); // 20Record<Size, number> demands a price for every size, so adding a new size later makes the table fail until it is priced. Typing the parameter as Size means 'huge' is refused. In JavaScript it gave undefined.
Utility types in the interview
The helpers, how they work, and the Omit surprise.
You are rarely asked to write these from scratch. You are often asked to say what they do.
- "What is a utility type?" A built-in generic type that makes a new type from an existing one, such as
Partial<T>orPick<T, K>. - "What is the difference between
PickandOmit?"Pickkeeps the named properties.Omitremoves them.Pickchecks that the keys exist, andOmitdoesn't. - "How would you type an update function?" Take a
Partial<T>for the changes, and return aT. - "What does
Record<K, V>do?" It builds an object type with keysKand valuesV. With a union of exact words asK, every key is required. - "How is
Partialimplemented?" A mapped type:{ [K in keyof T]?: T[K] }. - "What does
ReturnTypedo?" It gives the type a function returns. It is used asReturnType<typeof myFunction>. - "Is
Readonly<T>deep?" No, only the top-level properties. - "How do you make only some properties optional?" Combine them:
Partial<Pick<T, "a">> & Omit<T, "a">. - Say it simply. "Utility types reshape an existing type, so I write the shape once and derive the rest" is a good answer.
Updated 2026-10-17
Made by VeraStack Labs