Skip to content
The Switch · JavaScript to TypeScript · Lesson 8

Utility types

Reshape a type you already have, instead of writing it again.

Contents
Part 1 of 7 · The hook

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.

Part 2 of 7 · In JavaScript

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.

JavaScriptOpen in TypeScript Playground (opens in a new tab)
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);
Output
Asha

We 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.

JavaScriptOpen in TypeScript Playground (opens in a new tab)
function toPublic(user) {  return user;}const asha = { id: 1, name: "Asha", email: "asha@example.com", password: "chai123" };console.log(toPublic(asha));
Output
{ 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.

JavaScriptOpen in TypeScript Playground (opens in a new tab)
const prices = {  chai: 10,  coffee: 20,};console.log(prices.lassi);
Output
undefined

Remember: in JavaScript, nothing connects the edit form, the public card and the price list to the original shape.

Part 3 of 7 · In TypeScript

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.

TypeScriptOpen in TypeScript Playground (opens in a new tab)
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);
Output
Asha Rao
asha@example.com

Now 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.

TypeScriptOpen in TypeScript Playground (opens in a new tab)
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));
Output
{ 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.

Part 4 of 7 · How it works

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.

UtilityWhat 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.

TypeScriptOpen in TypeScript Playground (opens in a new tab)
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 }));
Output
{ 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.

TypeScriptOpen in TypeScript Playground (opens in a new tab)
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);
Output
{ id: 1, name: 'Asha' }

Misspell a key in Pick, and you get an error, because Pick only accepts keys that exist.

TypeScriptOpen in TypeScript Playground (opens in a new tab)
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.

TypeScriptOpen in TypeScript Playground (opens in a new tab)
function makeOrder(table: number) {  return { table, items: [] as string[], paid: false };}type OrderTicket = ReturnType<typeof makeOrder>;const ticket: OrderTicket = makeOrder(4);console.log(ticket);
Output
{ 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".

TypeScriptOpen in TypeScript Playground (opens in a new tab)
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);
Output
{ 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.

TypeScriptOpen in TypeScript Playground (opens in a new tab)
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.

Part 5 of 7 · Watch 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.

TypeScriptOpen in TypeScript Playground (opens in a new tab)
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.

Part 6 of 7 · Practise

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

JavaScriptOpen in TypeScript Playground (opens in a new tab)
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 added

Stuck?

Show a solution
TypeScriptOpen in TypeScript Playground (opens in a new tab)
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

JavaScriptOpen in TypeScript Playground (opens in a new tab)
function publicCard(account) {  return account;}console.log(publicCard({ id: 1, name: "Asha", phone: "98450", password: "chai123" })); // leaks the phone and password

Stuck?

Show a solution
TypeScriptOpen in TypeScript Playground (opens in a new tab)
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

JavaScriptOpen in TypeScript Playground (opens in a new tab)
const priceBySize = {  small: 10,  medium: 15,  large: 20,};function priceOf(size) {  return priceBySize[size];}console.log(priceOf("large")); // 20console.log(priceOf("huge")); // undefined

Stuck?

Show a solution
TypeScriptOpen in TypeScript Playground (opens in a new tab)
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"); // 20

Record<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.

Part 7 of 7 · Interview notes

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> or Pick<T, K>.
  • "What is the difference between Pick and Omit?" Pick keeps the named properties. Omit removes them. Pick checks that the keys exist, and Omit doesn't.
  • "How would you type an update function?" Take a Partial<T> for the changes, and return a T.
  • "What does Record<K, V> do?" It builds an object type with keys K and values V. With a union of exact words as K, every key is required.
  • "How is Partial implemented?" A mapped type: { [K in keyof T]?: T[K] }.
  • "What does ReturnType do?" It gives the type a function returns. It is used as ReturnType<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