TypeScript Utility Types: Partial, Pick, Omit, Record & More Explained
TypeScript ships a set of generic types built into the language itself, designed to transform one type into another without redeclaring it from scratch. Once you know Partial<T>, Pick<T, K>, and the half-dozen others in this guide, an enormous share of “how do I type this slightly-different version of a type I already have” questions have a one-line answer instead of a hand-written duplicate interface.
Partial<T> — Every Property Becomes Optional
interface User { id: string; name: string; email: string;}
function updateUser(id: string, updates: Partial<User>): void { // updates might have just { name: "New Name" }, or any subset of User's fields}
updateUser("1", { name: "Alice Updated" }); // fine — everything else is optionalupdateUser("1", { name: "Alice", email: "a@x.com" }); // also finePartial<User> produces a type identical to User but with every property suffixed ?. This is the standard shape for update/patch operations — a PATCH endpoint or a form that lets users edit one field at a time shouldn’t require every field to be resubmitted, and Partial<T> expresses that without duplicating User’s definition with every property manually marked optional.
Required<T> — The Opposite: Every Property Becomes Mandatory
interface Config { apiUrl?: string; timeout?: number;}
function finalizeConfig(config: Required<Config>): void { console.log(config.apiUrl.length, config.timeout.toFixed(0)); // no undefined checks needed}
finalizeConfig({ apiUrl: "https://api.example.com", timeout: 5000 }); // both required nowfinalizeConfig({ apiUrl: "https://api.example.com" }); // Error: timeout is missingUseful for the exact inverse situation: a config type is defined with optional fields (because defaults fill them in earlier), but a function deep in the pipeline needs to guarantee every field has already been resolved by the time it receives the object.
Readonly<T> — Every Property Becomes Immutable (Compile-Time Only)
interface Point { x: number; y: number;}
function freeze(point: Point): Readonly<Point> { return point;}
const p = freeze({ x: 1, y: 2 });p.x = 5; // Error: Cannot assign to 'x' because it is a read-only propertyLike the readonly modifier covered in the interfaces guide, Readonly<T> is a compile-time-only guarantee — it doesn’t call Object.freeze() under the hood, so nothing stops a runtime mutation via a type assertion or plain JavaScript code that bypasses the type system. For genuine runtime immutability, pair it with an actual Object.freeze() call.
Pick<T, K> — Select a Subset of Properties
interface User { id: string; name: string; email: string; passwordHash: string; createdAt: Date;}
type PublicUser = Pick<User, "id" | "name" | "email">;// { id: string; name: string; email: string } — no passwordHash, no createdAt
function getPublicProfile(user: User): PublicUser { return { id: user.id, name: user.name, email: user.email };}Pick<T, K> is exactly what it sounds like: take T, keep only the properties named in the union K. This is the standard way to derive a “safe to expose externally” type from a full internal type — PublicUser can never accidentally include passwordHash, because the type itself doesn’t have that property, not because a developer remembered to leave it out of an object literal.
Omit<T, K> — The Inverse: Exclude Specific Properties
type UserWithoutPassword = Omit<User, "passwordHash">;// Everything from User except passwordHash
type CreateUserInput = Omit<User, "id" | "createdAt">;// What a client sends when creating a user — id and createdAt are server-generatedOmit and Pick solve the same underlying problem from opposite directions: Pick is clearer when you’re keeping a small number of fields out of many; Omit is clearer when you’re excluding a small number of fields from an otherwise-complete type. CreateUserInput above reads more naturally as “everything except the server-generated fields” than it would as a Pick listing every other field by name.
Record<K, T> — Build an Object Type From a Set of Keys
type Role = "admin" | "editor" | "viewer";
const rolePermissions: Record<Role, string[]> = { admin: ["read", "write", "delete"], editor: ["read", "write"], viewer: ["read"],};// Record forces every key in the union to be present — a missing key is a compile errorconst incomplete: Record<Role, string[]> = { admin: ["read", "write", "delete"], editor: ["read", "write"], // Error: Property 'viewer' is missing};Record<K, T> is the type-level equivalent of the index signature covered in the interfaces guide, but for a known, finite set of keys rather than an open-ended dynamic one — and unlike a plain index signature, Record with a literal union K actually enforces that every key in the union is present, catching the “forgot to add the new role’s permissions” mistake at compile time the moment a new value is added to the Role union.
Exclude<T, U> and Extract<T, U> — Filtering Unions
type Status = "pending" | "active" | "closed" | "archived";
type ActiveStatus = Exclude<Status, "closed" | "archived">; // "pending" | "active"type InactiveStatus = Extract<Status, "closed" | "archived">; // "closed" | "archived"Exclude<T, U> removes every member of U from union T; Extract<T, U> keeps only the members T and U have in common. These are Pick/Omit’s counterparts for unions rather than object shapes — reach for Exclude/Extract when filtering down a set of allowed literal values, and Pick/Omit when filtering down an object’s properties.
NonNullable<T> — Strip null and undefined
function getUser(id: string): User | null | undefined { // ...}
type DefiniteUser = NonNullable<ReturnType<typeof getUser>>; // UserA common building block inside other generic utilities, and directly useful whenever you have a nullable type from one source and need to describe “the same type, guaranteed present” for a downstream consumer that has already handled the nullable case.
ReturnType<T> — Extract a Function’s Return Type
function createUser(name: string, email: string) { return { id: crypto.randomUUID(), name, email, createdAt: new Date() };}
type NewUser = ReturnType<typeof createUser>;// { id: string; name: string; email: string; createdAt: Date } — derived, not hand-writtenReturnType<typeof fn> is genuinely useful specifically because it keeps a derived type in sync automatically — if createUser’s implementation changes to add a new field, NewUser picks up the change without anyone needing to remember to update a separately hand-written interface. The typeof here matters: ReturnType operates on a type, and createUser the identifier refers to a value, so typeof createUser converts it to its type first.
Parameters<T> — Extract a Function’s Parameter Types as a Tuple
function sendEmail(to: string, subject: string, body: string) { ... }
type SendEmailArgs = Parameters<typeof sendEmail>; // [string, string, string]
function logAndSend(...args: SendEmailArgs) { console.log("Sending email with args:", args); sendEmail(...args);}Useful for wrapping an existing function (logging, retrying, rate-limiting) while keeping its exact parameter signature in sync automatically, rather than duplicating and maintaining the parameter list separately.
Awaited<T> — Unwrap a Promise Type
async function fetchUser(): Promise<User> { // ...}
type FetchedUser = Awaited<ReturnType<typeof fetchUser>>; // User, not Promise<User>// Awaited correctly unwraps nested promises tootype Nested = Promise<Promise<string>>;type Flat = Awaited<Nested>; // string, not Promise<string>Awaited<T> exists because ReturnType on an async function gives you Promise<T>, not T — and unwrapping a Promise type manually (especially a nested one) isn’t trivial to express without it. This combination, Awaited<ReturnType<typeof someAsyncFn>>, is common enough in real codebases that it’s worth recognizing on sight.
Combining Utility Types
interface User { id: string; name: string; email: string; passwordHash: string; createdAt: Date; updatedAt: Date;}
// A realistic "update user" input type, composed from several utilities at oncetype UpdateUserInput = Partial<Pick<User, "name" | "email">>;// { name?: string; email?: string } — only these two fields, both optional
// A realistic "public API response" typetype UserResponse = Readonly<Omit<User, "passwordHash">>;// Everything except passwordHash, and immutable once constructedThis is how utility types are actually used in production code — composed together, not used in isolation, to derive exactly the shape a specific function or API boundary needs directly from one canonical source type, instead of maintaining several hand-written near-duplicates of User that drift out of sync over time.
Utility Types at a Glance
| Utility | What it does |
|---|---|
Partial<T> | Every property becomes optional |
Required<T> | Every property becomes mandatory |
Readonly<T> | Every property becomes immutable (compile-time only) |
Pick<T, K> | Keep only the listed properties |
Omit<T, K> | Remove the listed properties |
Record<K, T> | Build an object type with keys K, all valued T |
Exclude<T, U> | Remove union members present in U |
Extract<T, U> | Keep only union members present in U |
NonNullable<T> | Remove null and undefined from a type |
ReturnType<T> | The return type of a function type |
Parameters<T> | The parameter types of a function type, as a tuple |
Awaited<T> | Unwrap a Promise<T> (including nested promises) down to T |
Frequently Asked Questions
Are utility types a separate feature, or built from the same tools I already know? The latter — every utility type in this guide is implemented internally using generics, mapped types, and conditional types (the subject of the next guide). They ship in TypeScript’s standard library specifically because these particular transformations come up constantly, but nothing about them is magic; you could write your own equivalents.
Does Partial<T> affect the underlying object at runtime? No — like everything else in TypeScript’s type system, it’s fully erased at compile time. Partial<User> doesn’t change how an object literal is constructed or stored; it only changes what the compiler will let you pass without every field present.
Can I combine Pick and Partial in either order? Yes, and the order matters for readability more than correctness: Partial<Pick<User, "name" | "email">> (pick first, then make optional) and Pick<Partial<User>, "name" | "email"> (make everything optional, then pick two) produce the same resulting type here, but for more complex compositions, applying the narrowing operation (Pick/Omit) before the modifying one (Partial/Required/Readonly) is usually easier to reason about.
When should I write a custom mapped type instead of composing built-in utility types? When the built-ins genuinely can’t express what you need — for instance, making only some properties optional based on a naming pattern, or deeply/recursively applying Partial to nested objects (the built-in Partial<T> is shallow — it doesn’t recurse into nested object properties). The next guide covers writing exactly these custom transformations.
Is Record<string, T> the same as an index signature { [key: string]: T }? Functionally, yes, for the open-ended string key case — they produce equivalent types. Record becomes meaningfully different (and more useful) specifically when K is a literal union rather than the wide string type, since only then does it enforce that every specific key is present.
Do all of these utility types work identically with interface and type-declared shapes? Yes — utility types operate on the resulting type shape, not on how it was originally declared, so Partial<SomeInterface> and Partial<SomeTypeAlias> behave identically as long as the underlying shapes are equivalent.
What’s Next
Built-in utility types cover the transformations you’ll need most often — but eventually you’ll hit a shape TypeScript’s standard library doesn’t provide a name for. Next: Mapped and Conditional Types, where you’ll learn to build your own Partial, Pick, and beyond from scratch, including the recursive and pattern-based transformations the built-ins can’t do.