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 · · 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.

#TypeScript #JavaScript #Web Development
Takina 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.

#TypeScript #JavaScript #Web Development
Takina 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.

#TypeScript #JavaScript #Web Development