TypeScript Utility Types Explained: Partial, Pick, Omit & More
TypeScript utility types like Partial, Pick, Omit, and Record transform existing types instead of redeclaring them. How the common ones work, with examples.
TypeScript’s utility types are built-in generic types that transform an existing type into a new one, so you don’t have to hand-write a near-duplicate interface every time you need a slightly different shape. Partial<T>, Pick<T, K>, Omit<T, K>, and a handful of others cover the vast majority of everyday type transformations, and they ship with the compiler — no import required.
Why derive types instead of redeclaring them
Without utility types, it’s tempting to write a second interface every time a function needs a variant of an existing shape:
interface User {
id: string;
name: string;
email: string;
role: "admin" | "member";
}
// A hand-written, easy-to-drift duplicate:
interface UserUpdate {
name?: string;
email?: string;
role?: "admin" | "member";
}
The problem is drift: add a field to User and UserUpdate silently falls out of sync. Utility types solve this by deriving the second shape from the first, so they update together:
type UserUpdate = Partial<Omit<User, "id">>;
Partial and Required
Partial<T> makes every property of T optional. It’s the standard shape for update payloads, form state, and configuration objects where any field might be left unset.
function updateUser(id: string, changes: Partial<User>) {
// changes.name, changes.email, changes.role are all optional
}
Required<T> is the inverse — it strips the optional modifier from every property, which is useful after merging user-supplied config with defaults, when you want the compiler to confirm every field is now guaranteed to exist.
Pick and Omit
Pick<T, K> builds a new type containing only the listed keys. Omit<T, K> does the opposite — everything except the listed keys.
type UserPreview = Pick<User, "id" | "name">;
// { id: string; name: string }
type UserWithoutId = Omit<User, "id">;
// { name: string; email: string; role: "admin" | "member" }
These are the two most reached-for utility types in practice: Pick for narrowing a type down to what an API response or UI component actually needs, Omit for excluding a field like a server-generated id or createdAt from a creation payload.
Record
Record<K, T> builds an object type where every key of K maps to a value of type T. It’s the type-level equivalent of a dictionary or lookup table.
type RolePermissions = Record<"admin" | "member", string[]>;
// { admin: string[]; member: string[] }
This shows up constantly for maps keyed by a union of string literals — status codes to labels, feature flags to booleans, locale codes to translated strings.
Readonly
Readonly<T> marks every property as immutable at the type level, so an assignment to any of them is a compile error. It doesn’t freeze the object at runtime — pair it with Object.freeze() if you need that guarantee too — but it catches accidental mutation of values that should flow one-way, like React props or Redux-style state.
function render(props: Readonly<User>) {
props.name = "changed"; // compile error
}
Composing utility types
The real leverage comes from combining them, as in the UserUpdate example above. Another common pattern is narrowing then making optional:
type ProfileForm = Partial<Pick<User, "name" | "email">>;
// { name?: string; email?: string }
Read these compositions inside-out: Pick<User, "name" | "email"> first narrows to a two-field type, then Partial<...> makes both fields optional. Nesting utility types like this replaces what would otherwise be several hand-maintained interfaces with one derived expression that updates automatically whenever the source type changes.
Comparing the core utility types
| Utility type | What it does | Typical use |
|---|---|---|
Partial<T> | Makes all properties optional | Update payloads, form state |
Required<T> | Makes all properties mandatory | Post-merge config validation |
Pick<T, K> | Keeps only the listed keys | API response shaping, view models |
Omit<T, K> | Removes the listed keys | Creation payloads, excluding server fields |
Record<K, T> | Builds a keyed lookup type | Maps, dictionaries, flag tables |
Readonly<T> | Marks all properties immutable | Props, one-way state |
When to reach for generics instead
Utility types are themselves built using TypeScript’s generics system — Partial<T> is just a generic type alias under the hood. If you find yourself repeating the same custom transformation across a codebase (say, a type that recursively makes every nested property optional), it’s often worth writing your own generic utility type rather than nesting the built-in ones several layers deep. The built-ins cover the common cases; generics cover everything else.
The takeaway
Partial, Pick, Omit, Record, Required, and Readonly let a type be derived from another type instead of copy-pasted and maintained separately. Reach for Pick and Omit when a function only needs part of a shape, Partial for anything representing an in-progress edit, and Record for keyed lookups. If you’re just getting started with TypeScript, these six cover most of what comes up before generics are needed at all.
Keep reading
Takina · · 4 min read TypeScript Abstract Classes, Explained
Abstract classes in TypeScript define shared implementation plus methods subclasses must fill in. How they differ from interfaces and when to reach for them.
Takina · · 3 min read TypeScript's never Type, Explained
never represents values that can't exist — it marks unreachable code, exhaustive switches, and functions that always throw or loop forever.
Takina · · 4 min read TypeScript readonly Modifiers Explained
TypeScript's readonly keyword blocks reassignment at compile time for properties, arrays, and tuples — with no runtime enforcement at all.