TypeScript Utility Types Explained

TL;DR: TypeScript utility types are built-in generic types that transform an existing type into a new one without you rewriting it. The ones you reach for daily are Partial (make every property optional), Pick and Omit (keep or drop keys), Record (build a keyed object type), and ReturnType, Parameters, and Awaited (pull types out of functions and promises). They ship with the compiler, so there is nothing to import.

What is a utility type?

A utility type is a generic type that takes one or more types as arguments and returns a transformed type. They live in TypeScript's standard library (lib.es5.d.ts and its siblings) and are globally available. Instead of hand-writing a second interface that is "the same as User but every field is optional", you write Partial<User> and let the compiler derive it. That keeps a single source of truth: change User once and every derived type updates automatically.

It helps to group them into three families:

  • Object-shape transforms: Partial, Required, Readonly, Pick, Omit, Record.
  • Union filters: Exclude, Extract, NonNullable.
  • Function and inference helpers: ReturnType, Parameters, Awaited.

The rest of this guide walks each one with a minimal example and a note on when to reach for it.

Object-shape transforms

Partial<T>

Makes all properties of T optional. This is the go-to type for update or patch payloads, where a caller changes only a subset of fields.

interface User {
  id: number;
  name: string;
  email: string;
}

// { id?: number; name?: string; email?: string; }
function updateUser(id: number, patch: Partial<User>): void {
  // apply only the fields that were provided
}

updateUser(1, { email: "new@example.com" }); // ok, other fields omitted

Required<T>

The opposite of Partial: it strips every ? and makes all properties required. Reach for it after you have filled in defaults and want to guarantee to downstream code that nothing is missing.

interface Config {
  host?: string;
  port?: number;
}

// { host: string; port: number; }
function connect(config: Required<Config>): void {
  console.log(config.host, config.port); // both guaranteed present
}

Readonly<T>

Marks every property readonly, so any reassignment becomes a compile error. Use it for values that must not mutate after creation, such as configuration or frozen state. Note that it is shallow: nested objects are still mutable.

interface Point {
  x: number;
  y: number;
}

const origin: Readonly<Point> = { x: 0, y: 0 };
// origin.x = 5; // Error: Cannot assign to 'x' because it is a read-only property.

Pick<T, K>

Builds a new type from a subset of T's keys. Use it to expose a narrow view of a large type, like a list preview or a public DTO.

interface Article {
  id: number;
  title: string;
  body: string;
  authorId: number;
}

// { id: number; title: string; }
type ArticlePreview = Pick<Article, "id" | "title">;

const preview: ArticlePreview = { id: 1, title: "Hello" };

Omit<T, K>

The inverse of Pick: keep everything except the listed keys. Reach for it when it is easier to say what to remove than what to keep, for example a "create" payload that omits the server-generated id.

interface Article {
  id: number;
  title: string;
  body: string;
  authorId: number;
}

// everything except id and authorId
type DraftArticle = Omit<Article, "id" | "authorId">;

const draft: DraftArticle = { title: "Draft", body: "..." };

Record<K, T>

Constructs an object type whose keys are K and whose values are all T. Use it for lookup tables, dictionaries, and enum-keyed maps. When K is a finite union, the compiler forces you to provide every key.

type Role = "admin" | "editor" | "viewer";

// { admin: boolean; editor: boolean; viewer: boolean; }
const permissions: Record<Role, boolean> = {
  admin: true,
  editor: true,
  viewer: false,
};

// a dictionary of arbitrary string keys
const scores: Record<string, number> = { alice: 10, bob: 7 };

Union filters

Exclude<T, U>

Removes from union T any members that are assignable to U. Use it to subtract cases from a union, such as removing a transient state.

type Status = "idle" | "loading" | "success" | "error";

// "idle" | "success" | "error"
type Settled = Exclude<Status, "loading">;

Extract<T, U>

The complement of Exclude: keep only the members of T that are assignable to U. Use it to select a subset of a union by a shared trait.

type Event =
  | { kind: "click"; x: number; y: number }
  | { kind: "key"; code: string }
  | { kind: "scroll"; delta: number };

// the click and scroll arms only
type PointerEvent = Extract<Event, { kind: "click" | "scroll" }>;

NonNullable<T>

Removes null and undefined from a type. It is essentially Exclude<T, null | undefined>. Use it after a null check, or to describe a value you have already validated as present.

type MaybeName = string | null | undefined;

// string
type Name = NonNullable<MaybeName>;

Function and inference helpers

ReturnType<T>

Extracts the return type of a function type. Use it to stay in sync with a function's output without repeating its shape. The typeof fn prefix turns a value into its type first.

function createUser(name: string) {
  return { id: Date.now(), name, active: true };
}

// { id: number; name: string; active: boolean; }
type NewUser = ReturnType<typeof createUser>;

Parameters<T>

Extracts a function's parameters as a tuple type. Use it to wrap or forward calls, for example a logging wrapper that accepts the same arguments as the original.

function sendEmail(to: string, subject: string, body: string) {}

// [to: string, subject: string, body: string]
type EmailArgs = Parameters<typeof sendEmail>;

// grab a single parameter by index
type Subject = Parameters<typeof sendEmail>[1]; // string

Awaited<T>

Unwraps the value inside a Promise, recursively. It was added in TypeScript 4.5 to model await at the type level. Use it whenever you need the resolved type of an async function, especially combined with ReturnType.

async function fetchCount(): Promise<number> {
  return 42;
}

// number
type Count = Awaited<ReturnType<typeof fetchCount>>;

// nested promises are unwrapped too
type A = Awaited<Promise<Promise<string>>>; // string

Quick reference table

UtilityTurns...Into...
Partial<T>all propsoptional
Required<T>all propsrequired
Readonly<T>all propsreadonly
Pick<T, K>Tonly keys K
Omit<T, K>TT without keys K
Record<K, T>keys Kobject with T values
Exclude<T, U>union TT minus U members
Extract<T, U>union Tonly T members in U
NonNullable<T>TT without null/undefined
ReturnType<F>function Fits return type
Parameters<F>function Fits params tuple
Awaited<T>Promise<T>the resolved value type

Combining utility types

Utility types compose, and that is where they earn their keep. A common pattern is a "create" input that drops server-owned fields, plus an "update" input that makes the rest optional:

interface DbUser {
  id: string;
  email: string;
  createdAt: Date;
  name: string;
}

// caller supplies email and name; the server sets id and createdAt
type CreateUserInput = Omit<DbUser, "id" | "createdAt">;

// a patch that can touch any editable field
type UpdateUserInput = Partial<Omit<DbUser, "id" | "createdAt">>;

Because both derive from DbUser, adding a field to DbUser flows through to both automatically. That is the whole point: no drift between your model and the types around it.

How utility types are built

Most object-shape utilities are just mapped types with a modifier. Partial, for instance, is roughly this:

type MyPartial<T> = {
  [K in keyof T]?: T[K];
};

Once you understand mapped types and generics, you can write your own transforms, like a deep-readonly or a "nullable every field" helper, when the built-ins do not cover your case.

Common gotchas

  • Readonly and Partial are shallow. Nested objects keep their original modifiers.
  • Pick and Omit take key names as string literals; a typo in a key is a compile error, which is a feature.
  • Record<string, T> allows any string key, so property access is not narrowed for missing keys unless noUncheckedIndexedAccess is on.
  • ReturnType and Parameters need a function type. Pass typeof fn, not fn itself.

If you hit a red squiggle like Argument of type X is not assignable to parameter of type Y while wiring these together, that is TS2345, and our guide to fixing error TS2345 walks through every common cause.

Next steps

  • Work through the interactive Utility Types lesson to practice each one with exercises.
  • Learn the mapped types that power these utilities so you can build your own.
  • Browse the full TypeScript course to see where utility types fit in the bigger picture.