Utility Types

TypeScript · Lesson 15 of 28 6 min read Updated

Once you have a type such as Student, you often need variations of it: a version where every property is optional for an update form, a version without the password for a public profile, or a read-only version for a cache. Writing each variation by hand means repeating yourself, and the copies drift apart when the original changes.

TypeScript ships with utility types: generic types that build a new type from an existing one. In this lesson you will learn the ones you will use every week, with realistic examples for each.

Partial, Required and Readonly

These three change the modifiers of every property:

  • Partial<T> makes every property optional.
  • Required<T> makes every property required.
  • Readonly<T> makes every property read-only.

Partial is perfect for update functions, where the caller sends only the fields that changed:

TypeScriptRun it yourself
type Student = {
  id: number;
  name: string;
  city: string;
  marks: number;
};

function updateStudent(student: Student, changes: Partial<Student>): Student {
  return { ...student, ...changes };
}

const aarav: Student = { id: 1, name: "Aarav", city: "Rohtak", marks: 72 };

const moved = updateStudent(aarav, { city: "Jaipur" });
const improved = updateStudent(moved, { marks: 81 });

console.log(aarav);
console.log(improved);

const frozen: Readonly<Student> = Object.freeze({ ...improved });
console.log(Object.isFrozen(frozen));
Output
{ id: 1, name: 'Aarav', city: 'Rohtak', marks: 72 }
{ id: 1, name: 'Aarav', city: 'Jaipur', marks: 81 }
true

Readonly<T> makes the compiler reject frozen.marks = 90. In fact, Object.freeze() already returns a Readonly type, so the annotation simply makes it visible.

Pick and Omit

Pick<T, Keys> keeps only the listed properties. Omit<T, Keys> keeps everything except the listed ones. Keys are written as a union of string literals.

TypeScriptRun it yourself
type User = {
  id: number;
  name: string;
  email: string;
  passwordHash: string;
  createdAt: string;
};

type PublicProfile = Pick<User, "id" | "name">;
type NewUserInput = Omit<User, "id" | "createdAt">;

function toPublic(user: User): PublicProfile {
  return { id: user.id, name: user.name };
}

function createUser(input: NewUserInput, nextId: number): User {
  return { ...input, id: nextId, createdAt: "2026-10-30" };
}

const isha = createUser({ name: "Isha", email: "isha@example.com", passwordHash: "x9f2" }, 7);
console.log(toPublic(isha));
console.log(Object.keys(isha).length);
Output
{ id: 7, name: 'Isha' }
5

If you later add a phone property to User, NewUserInput gets it automatically, while PublicProfile stays limited to id and name. The derived types can never fall out of date.

Omit does not check that the keys exist, so a typo such as Omit<User, "createAt"> silently removes nothing. Pick does check:

TypeScript
type User = { id: number; name: string; createdAt: string };

type A = Omit<User, "createAt">;
type B = Pick<User, "nmae">;

const input: Omit<User, "id"> = { name: "Rohan" };
Output
app.ts(4,21): error TS2344: Type '"nmae"' does not satisfy the constraint 'keyof User'.
app.ts(6,7): error TS2741: Property 'createdAt' is missing in type '{ name: string; }' but required in type 'Omit<User, "id">'.

Line 3 has no error even though createAt is a typo. Keep that in mind when an Omit seems to "not work".

Record

Record<Keys, Value> builds an object type with the given keys, all with the same value type. With a union of literals as keys, TypeScript makes sure every key is present:

TypeScript
type Day = "mon" | "tue" | "wed" | "thu" | "fri";
type Timetable = Record<Day, string[]>;

const classSix: Timetable = {
  mon: ["Maths", "Hindi"],
  tue: ["Science"],
  wed: ["English", "Maths"],
  thu: ["Art"],
  fri: ["Sports"],
};

Leave out fri and you get an error naming the missing property. With Record<string, number>, any string key is allowed, as you saw in Object Types.

Exclude, Extract and NonNullable

These three work on union types, removing or keeping members:

  • Exclude<Union, Removed> removes members.
  • Extract<Union, Kept> keeps only matching members.
  • NonNullable<T> removes null and undefined.
TypeScriptRun it yourself
type Payment = "upi" | "card" | "cash" | "wallet";

type Online = Exclude<Payment, "cash">;          // "upi" | "card" | "wallet"
type Instant = Extract<Payment, "upi" | "cash">; // "upi" | "cash"
type MaybeName = string | null | undefined;
type Name = NonNullable<MaybeName>;              // string

function onlineFee(mode: Online, amount: number): number {
  return mode === "card" ? Math.round(amount * 0.02) : 0;
}

const greet = (name: Name) => `Hello, ${name}`;

console.log(onlineFee("card", 2500), onlineFee("upi", 2500));
console.log(greet("Ananya"));
Output
50 0
Hello, Ananya

Calling onlineFee("cash", 100) would now be a compile error, because "cash" was excluded from the type.

Types from functions: ReturnType, Parameters and Awaited

Sometimes the "source" type is a function. ReturnType<F> gives the type a function returns, Parameters<F> gives its parameter types as a tuple, and Awaited<T> unwraps a Promise, which is useful for async functions.

TypeScriptRun it yourself
async function fetchSeatMap(train: number, date: string) {
  return { train, date, free: [12, 15, 33], fare: 1450 };
}

type SeatMap = Awaited<ReturnType<typeof fetchSeatMap>>;
// { train: number; date: string; free: number[]; fare: number }

type FetchArgs = Parameters<typeof fetchSeatMap>; // [train: number, date: string]

function summary(map: SeatMap): string {
  return `${map.train} on ${map.date}: ${map.free.length} seats at ₹${map.fare}`;
}

const args: FetchArgs = [12951, "2026-11-14"];
const map = await fetchSeatMap(...args);
console.log(summary(map));
Output
12951 on 2026-11-14: 3 seats at ₹1450

You did not have to write the SeatMap type at all: it is derived from what the function really returns. typeof fetchSeatMap gets the function's type, which keyof, typeof & Indexed Access explains.

NoInfer

NoInfer<T> (added in TypeScript 5.4) tells TypeScript not to use a particular argument when it infers a type parameter. In this function, the list of stations should decide the type, and the default must be one of them:

TypeScript
function pickStation<S extends string>(stations: S[], fallback: NoInfer<S>): S {
  return stations[0] ?? fallback;
}

pickStation(["Pune", "Lonavala"], "Pune");
pickStation(["Pune", "Lonavala"], "Mumbai");
Output
app.ts(6,35): error TS2345: Argument of type '"Mumbai"' is not assignable to parameter of type '"Lonavala" | "Pune"'.

Without NoInfer, TypeScript would simply add "Mumbai" to the inferred union and accept the call.

UtilityResult
Partial<T>All properties optional
Required<T>All properties required
Readonly<T>All properties read-only
Pick<T, K>Only keys K
Omit<T, K>All keys except K
Record<K, V>Object with keys K and values V
Exclude<U, X> / Extract<U, X>Remove / keep union members
NonNullable<T>Remove null and undefined
ReturnType<F> / Parameters<F>A function's return / parameter types
Awaited<T>The value inside a Promise
NoInfer<T>Block inference from this position

Common mistakes

  • Copying a type by hand to make a small variation. Derive it with Partial, Pick or Omit so it stays in sync.
  • Trusting Omit to catch typos. It accepts any key. Double-check the names, or use Pick for the keys you keep.
  • Using Partial<T> where only some fields are optional. Combine instead: Pick<T, "id"> & Partial<Omit<T, "id">>.
  • Forgetting typeof. ReturnType<fetchSeatMap> is an error, because fetchSeatMap is a value. Write ReturnType<typeof fetchSeatMap>.

Key points

  • Utility types are built-in generics that create new types from existing ones.
  • Partial, Required and Readonly change every property's modifiers.
  • Pick and Omit select properties; Omit does not check key names.
  • Record, Exclude, Extract and NonNullable build objects and filter unions.
  • ReturnType, Parameters and Awaited derive types from functions, and NoInfer controls inference.

Exercise

A library app in Lucknow has type Book = { isbn: string; title: string; author: string; copies: number; addedOn: string }. Create these derived types without repeating any property: BookCard (only title and author), NewBook (everything except addedOn), and BookUpdate (isbn required, every other property optional). Write a function applyUpdate(book: Book, update: BookUpdate): Book and test it by changing the number of copies.

Show solution
TypeScript
type Book = { isbn: string; title: string; author: string; copies: number; addedOn: string };

type BookCard = Pick<Book, "title" | "author">;
type NewBook = Omit<Book, "addedOn">;
type BookUpdate = Pick<Book, "isbn"> & Partial<Omit<Book, "isbn">>;

function applyUpdate(book: Book, update: BookUpdate): Book {
  if (book.isbn !== update.isbn) throw new Error("Wrong book");
  return { ...book, ...update };
}

const draft: NewBook = { isbn: "978-93-0001", title: "Godan", author: "Premchand", copies: 3 };
const book: Book = { ...draft, addedOn: "2026-10-30" };
const card: BookCard = { title: book.title, author: book.author };

console.log(applyUpdate(book, { isbn: "978-93-0001", copies: 5 }).copies); // 5
console.log(card); // { title: 'Godan', author: 'Premchand' }

BookUpdate combines Pick and Partial<Omit<...>> with an intersection, so the ISBN is required and everything else is optional.

Quick Quiz

1. Which utility type makes every property optional?

2. What is Exclude<"upi" | "card" | "cash", "cash">?

3. Why is a typo in Omit<User, "createAt"> not reported?

4. How do you get the resolved return type of an async function load()?

Learn TypeScript with a mentor

Free lessons get you started. Our Full Stack Web Development training adds real projects and code reviews from working developers.

  • Project-based learning
  • Mentor code reviews
  • Free counselling call