Imagine you’ve already described the shape of a User in your code — name, email, age, all of it. Now you need a slightly different version: one where every field is optional (for a form draft), or one where you only keep the name and email, or one where nothing can be changed after creation. The lazy instinct is to copy the original type and edit it by hand. That works once. Then the original changes, your copy doesn’t, and the two quietly drift apart.
TypeScript has a better answer. It ships with a small set of utility types — built-in helpers that take an existing type and hand you back a transformed version of it. You don’t rewrite the shape; you describe the transformation. When the original type changes, every derived type updates automatically. This article walks through the ones you’ll actually reach for, what each does, and why they save you so much repeated typing.
What a utility type really is
A utility type is just a type that takes another type as input and produces a new type as output. Think of it like a function, except instead of working on values at runtime, it works on types at compile time. The input goes inside angle brackets — that <T> you keep seeing is a type parameter, a placeholder for “whatever type you pass in.”
Here’s the User type we’ll reuse throughout:
interface User {
id: number;
name: string;
email: string;
age: number;
}
Everything below transforms this one shape. Nothing recreates it.
Partial: make every field optional
Partial<T> takes a type and marks every one of its properties as optional. It’s perfect for “update” functions, where the caller only wants to change a field or two.
function updateUser(id: number, changes: Partial<User>) {
// changes can be { name: "Jane Doe" } — or { age: 30, email: "..." }
// every field is allowed, but none is required
}
updateUser(1, { name: "Jane Doe" }); // valid
updateUser(2, {}); // also valid
Without Partial, you’d write a whole second interface with question marks on every field, then keep it in sync forever. With it, the relationship is permanent: add a field to User, and Partial<User> gains that optional field for free.
Required: the exact opposite
Required<T> does the reverse — it strips away the optional markers and forces every property to be present. This is handy when a value arrives loose (lots of optional fields) but you’ve validated it and now want to guarantee everything is filled in.
interface Settings {
theme?: string;
fontSize?: number;
}
function applySettings(settings: Required<Settings>) {
// here, theme and fontSize are both guaranteed to exist
console.log(settings.theme.toUpperCase());
}
Readonly: lock it down
Readonly<T> keeps the same shape but marks every property as read-only, so the compiler refuses any attempt to reassign a field after the object is created. Use it when you want to pass data around without anyone accidentally mutating it.
const user: Readonly<User> = {
id: 1, name: "John Doe", email: "john@example.com", age: 28,
};
user.name = "Changed"; // Error: cannot assign to 'name', it is read-only
It's a compile-time guarantee
Readonly is checked by TypeScript while you write code, not enforced at runtime. The compiled JavaScript has no idea the property was meant to be frozen. If you need a true runtime lock, reach for Object.freeze() as well.
Pick: keep only the fields you want
Pick<T, Keys> builds a new type from an existing one by selecting a subset of its properties. You pass the source type and a list of the keys you want to keep.
type UserPreview = Pick<User, "id" | "name">;
// equivalent to: { id: number; name: string }
const preview: UserPreview = { id: 1, name: "John Doe" };
This is great for things like list views or API responses where you only need a slice of the full object. The field types come along automatically — id stays number, name stays string — so there’s nothing to copy.
Omit: keep everything except a few fields
Omit<T, Keys> is the mirror image of Pick. Instead of saying which fields to keep, you say which to remove, and you get everything else.
type PublicUser = Omit<User, "email" | "age">;
// equivalent to: { id: number; name: string }
const publicData: PublicUser = { id: 1, name: "Jane Doe" };
When should you pick Pick and when should you reach for Omit? A simple rule: if you’re keeping most of the object and dropping one or two fields, use Omit. If you’re keeping only a couple of fields out of many, use Pick. Both produce the same kind of result; you just choose the shorter description.
Record: build an object type from keys and values
Record<Keys, ValueType> constructs an object type where the keys are of one type and every value is of another. It’s the cleanest way to describe “a lookup table” or “a dictionary.”
type Role = "admin" | "editor" | "viewer";
const permissions: Record<Role, boolean> = {
admin: true,
editor: true,
viewer: false,
};
Because Role only allows three specific strings, TypeScript will complain if you forget one of them or add a key that isn’t a valid role. You get a complete, checked table instead of a loose object.
Exclude: remove members from a union
The previous helpers reshape object types. Exclude<T, U> works on union types — those “this or that or the other” types joined with |. It removes from T any member that’s also in U.
type Status = "draft" | "published" | "archived";
type ActiveStatus = Exclude<Status, "archived">;
// result: "draft" | "published"
This is useful when you have a master list of options and want to derive a narrower list from it without writing the narrower list out by hand. (There’s a sibling, Extract<T, U>, that does the opposite — it keeps only the members that are in U.)
ReturnType: grab a function’s output type
ReturnType<T> reaches into a function type and pulls out the type of whatever that function returns. You rarely want to type out a complex return shape twice, so this lets you reference it instead.
function createUser(name: string, email: string) {
return { id: Date.now(), name, email, active: true };
}
type NewUser = ReturnType<typeof createUser>;
// { id: number; name: string; email: string; active: boolean }
Notice the typeof createUser part. ReturnType expects a function type, not a function value, so typeof is what turns the actual function into its type. The payoff is that if you later add a field to the returned object, NewUser updates on its own.
These compose
Utility types stack. Want a type that has only id and name, but with both optional? Write Partial<Pick<User, "id" | "name">>. You can nest them as deeply as the situation calls for — each one just feeds its result into the next.
Quick reference table
| Utility | What it does | Small example |
|---|---|---|
Partial<T> |
Makes every property optional | Partial<User> |
Required<T> |
Makes every property required | Required<Settings> |
Readonly<T> |
Makes every property read-only | Readonly<User> |
Pick<T, K> |
Keeps only the listed keys | Pick<User, "id" | "name"> |
Omit<T, K> |
Removes the listed keys | Omit<User, "email"> |
Record<K, V> |
Builds an object type from keys + value type | Record<Role, boolean> |
Exclude<T, U> |
Removes union members found in U |
Exclude<Status, "archived"> |
ReturnType<T> |
Extracts a function’s return type | ReturnType<typeof createUser> |
Why this matters in practice
The thread running through all of these is single source of truth. You define a shape once, and every variation of it is derived from that definition rather than copied. When requirements change — a new field, a renamed property, a different return value — you edit one place and everything downstream follows along. That’s the difference between a type system that helps you and one that you constantly fight to keep in sync.
There’s a second benefit that’s easy to overlook: readability. Omit<User, "password"> tells the next person reading your code exactly what’s going on — “this is a User, minus the password.” A hand-rolled interface with most of User’s fields copied in says nothing about that intent. Utility types make your transformations self-documenting.
Recap
TypeScript’s utility types let you transform existing types instead of rewriting them:
PartialandRequiredflip every field’s optionality on or off.Readonlyprevents reassignment after creation (at compile time).PickandOmitcarve a subset out of an object type — one keeps, the other removes.Recordbuilds a checked key-to-value object type, ideal for lookup tables.Excludetrims members out of a union (withExtractas its keep-only twin).ReturnTypelifts a function’s return type so you never describe it twice.
Reach for these the moment you catch yourself about to copy a type and tweak it. Describe the transformation instead, and let TypeScript keep all the pieces aligned for you.