RizTech Academy logo
RizTech Academy
GenericsLesson 5 of 530 min

Built-in utility types: Partial, Pick, Omit and Record

TypeScript ships with a set of utility types — pre-built generic types that transform other types. Partial<T>, Pick<T, K>, Omit<T, K>, Record<K, V> and their friends save you from re-declaring variations of a type by hand, and using them fluently is a mark of everyday TypeScript competence. They are also generic types you now understand — and in the advanced-types module, you will build several yourself, which demystifies them completely.

The problem they solve

You have a User type. Now you need "a User with all fields optional" (for a partial update), and "a User without its password" (for an API response), and "just the id and name". Without utility types you would declare each variation by hand — repetitive, and they drift out of sync when User changes. Utility types derive each variation from User, so they stay in sync automatically.

interface User {
  id: string;
  name: string;
  email: string;
  password: string;
}

Partial<T> — all properties optional

Partial<T> makes every property of T optional:

type UserUpdate = Partial<User>;
// { id?: string; name?: string; email?: string; password?: string }

function updateUser(id: string, changes: Partial<User>): void {
  // changes may contain any subset of User's fields
}

updateUser("1", { name: "Kavita Joshi" });    // fine — just one field
updateUser("1", {});                           // fine — no fields
updateUser("1", { age: 5 });                   // error — 'age' is not a User field

Partial<User> is the classic type for an "update" or "patch" operation, where the caller supplies only the fields they want to change. It is derived from User, so adding a field to User automatically makes it available (optionally) in every Partial<User>.

Required<T> and Readonly<T> — the mirrors

The opposites of Partial:

type FullUser = Required<User>;     // every property REQUIRED (removes all `?`)
type FrozenUser = Readonly<User>;   // every property READONLY (cannot be reassigned)

const u: Readonly<User> = { id: "1", name: "K", email: "k@x.com", password: "..." };
u.name = "X";    // error: Cannot assign to 'name' because it is a read-only property

Required<T> strips optionality (rarely needed but there when you have a type with optional fields you now want all of); Readonly<T> makes the whole shape immutable — useful for a value you want to guarantee nothing mutates.

Pick<T, K> and Omit<T, K> — selecting fields

Pick keeps only the named properties; Omit removes them:

type UserSummary = Pick<User, "id" | "name">;
// { id: string; name: string }

type SafeUser = Omit<User, "password">;
// { id: string; name: string; email: string }  — password removed

function toPublic(user: User): Omit<User, "password"> {
  const { password, ...rest } = user;
  return rest;
}

Pick<User, "id" | "name"> selects a subset — ideal for a summary or list view. Omit<User, "password"> drops sensitive or irrelevant fields — the standard way to type an API response that must not leak a password. Both are derived from User, so they track its changes, and both are checked: a key that is not in User is an error. These two are among the most-used utility types in real codebases — you reach for Omit constantly to strip a field.

Record<K, V> — an object type from keys and values

Record<K, V> builds an object type with keys of type K and values of type V:

type Prices = Record<string, number>;
// { [key: string]: number }  — any string key, number value

const menu: Prices = { chai: 15, samosa: 20 };

type StatusLabels = Record<"pending" | "paid" | "shipped", string>;
// { pending: string; paid: string; shipped: string }  — exactly these three keys, all required

const labels: StatusLabels = { pending: "Pending", paid: "Paid", shipped: "Shipped" };
const bad: StatusLabels = { pending: "Pending" };   // error: 'paid' and 'shipped' are missing

Record<string, number> is the readable way to type a dictionary (better than the index-signature syntax from the objects module). More powerfully, Record<"pending" | "paid" | "shipped", string> creates an object that must have exactly those three keys — perfect for a lookup table where every member of a union needs an entry, and the compiler ensures you did not miss one. This "a value for every key" guarantee is genuinely useful and hard to get any other way.

The extractors: ReturnType, Parameters, Awaited

A few utility types extract types from functions and promises — invaluable for typing code that relates to an existing function:

function createUser(name: string, age: number) {
  return { id: "1", name, age, createdAt: new Date() };
}

type NewUser = ReturnType<typeof createUser>;   // { id: string; name: string; age: number; createdAt: Date }
type Args = Parameters<typeof createUser>;       // [name: string, age: number]

type Data = Awaited<Promise<string>>;            // string — unwraps the Promise
type FetchResult = Awaited<ReturnType<typeof fetchUser>>;   // the resolved type of an async function

ReturnType<typeof fn> gives you the type a function returns without re-declaring it — so a type stays in sync with the function it comes from. Parameters<typeof fn> gives the parameter types as a tuple. Awaited<T> unwraps a Promise (or nested promises) to its resolved value — essential in the async module for typing what an async function actually produces. These "derive a type from existing code" utilities are how you avoid duplicating type information that already exists in a function signature.

The full toolkit, and the deeper lesson

There are more — NonNullable<T> (removes null/undefined), Exclude<T, U> and Extract<T, U> (for unions), InstanceType<T> — and you will learn them as you meet them. But the important realisation is this: these are not magic built-ins; they are generic types built from the type system's own features (mapped types, conditional types — the advanced-types module). Partial<T> is a few lines of mapped type; Omit is built from Pick and Exclude. When you reach the advanced-types module and build Partial and Pick yourself, these will stop being memorised incantations and become obvious consequences of tools you understand. For now: learn to reach for the right utility type instead of re-declaring a variation by hand — it keeps your types derived, in sync, and honest.

Check your work

The problem utility types solve. Deriving variations of a type (optional, subset, without a field) instead of re-declaring them by hand, so they stay in sync.

Partial<T>. All properties optional — the classic "update/patch" type.

Required<T> and Readonly<T>. All required (strips ?); all readonly (immutable shape).

Pick<T, K> and Omit<T, K>. Keep only the named keys; remove the named keys — for summaries and for stripping fields (e.g. a password from an API response).

Record<K, V>. An object type with K keys and V values — a readable dictionary, and with a key union, a "value for every key" table the compiler enforces.

ReturnType, Parameters, Awaited. Extract a function's return type, its parameter tuple, and a promise's resolved type — deriving types from existing code.

The deeper truth. Utility types are ordinary generic types built from mapped and conditional types — not magic; you will build several yourself in the advanced-types module.

The habit. Reach for the right utility type rather than re-declaring a variation by hand.

Practice

  1. Define a User interface. Derive Partial<User>, Readonly<User>, Pick<User, "id" | "name">, and Omit<User, "password">. Inspect each.
  2. Write an updateUser(id, changes: Partial<User>) and call it with a subset, an empty object, and a non-field (error).
  3. Write a toPublic(user): Omit<User, "password"> that strips the password.
  4. Use Record<"pending" | "paid" | "shipped", string> and confirm the compiler requires all three keys.
  5. Use ReturnType<typeof someFunction> to type a value from a function's return, and change the function to confirm the type follows.
  6. Use Awaited<...> to unwrap a Promise<string> and an async function's return type.
  7. Take a variation type you declared by hand and rewrite it using a utility type. Confirm it now tracks the base type's changes.

Official documentation

Next module — The Type System in Depth: the features TypeScript is actually chosen for.

Stuck on this lesson?

Being stuck is part of it — but being stuck alone for three days is not. Our internship programme pairs this curriculum with code review and one-to-one help from working developers, and it is free.

About the internship