Articles

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.

Takina Takina · · 4 min read
Code editor showing typed function signatures

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 typeWhat it doesTypical use
Partial<T>Makes all properties optionalUpdate payloads, form state
Required<T>Makes all properties mandatoryPost-merge config validation
Pick<T, K>Keeps only the listed keysAPI response shaping, view models
Omit<T, K>Removes the listed keysCreation payloads, excluding server fields
Record<K, T>Builds a keyed lookup typeMaps, dictionaries, flag tables
Readonly<T>Marks all properties immutableProps, 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.

Takina Takina · · 5 min read

JavaScript Custom Errors and Error Cause, Explained

JavaScript custom error classes and the Error cause option let you throw specific, chainable errors. How to define them, wrap failures, and handle them well.

#JavaScript #TypeScript #Web Development
Takina Takina · · 4 min read

Structural Typing vs Nominal Typing in TypeScript

Structural typing checks shape, not name — TypeScript treats two differently-named types as compatible if their members match, unlike nominal systems.

#TypeScript #JavaScript #Web Development
Takina Takina · · 5 min read

TypeScript Variance Explained

Variance decides when TypeScript accepts Array<Dog> where Array<Animal> is expected — covariant, contravariant, or invariant, explained with examples.

#TypeScript #JavaScript #Web Development