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
- Define a
Userinterface. DerivePartial<User>,Readonly<User>,Pick<User, "id" | "name">, andOmit<User, "password">. Inspect each. - Write an
updateUser(id, changes: Partial<User>)and call it with a subset, an empty object, and a non-field (error). - Write a
toPublic(user): Omit<User, "password">that strips the password. - Use
Record<"pending" | "paid" | "shipped", string>and confirm the compiler requires all three keys. - Use
ReturnType<typeof someFunction>to type a value from a function's return, and change the function to confirm the type follows. - Use
Awaited<...>to unwrap aPromise<string>and an async function's return type. - 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
- TypeScript — Utility Types — The complete reference for all of them.
- TypeScript — Partial, Pick, Omit, Record — The everyday four.
- TypeScript — ReturnType, Parameters, Awaited — The extractors.
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