Skip to content
Skip to content
DocsTypeScript ExpertexpertUtility Types In Depth
Chapter 9 of 10·expert·10 min read

Utility Types In Depth

Utility Type Chuyên Sâu

Master built-in utility types and build your own type library

Hover or tap any paragraph to see Vietnamese translation

What Are Utility Types?

TypeScript ships a set of global utility types that transform existing types. They are implemented with mapped types and conditional types — understanding their internals is what separates someone who looks them up from someone who builds their own.

Object Manipulation: Partial, Required, Readonly, Record

These four utility types modify the requiredness and mutability of object type properties.

partial-required.ts
1interface User {2  id: number;3  name: string;4  email: string;5  role: "admin" | "user";6}78// Partial<T>: all properties become optional9type UserUpdate = Partial<User>;10// { id?: number; name?: string; email?: string; role?: "admin" | "user" }1112// Internally:
readonly-record.ts
1// Readonly<T>: all properties become readonly2const config: Readonly<User> = {3  id: 1, name: "Alice", email: "alice@example.com", role: "admin",4};5// config.name = "Bob"; // Error: Cannot assign to 'name' — read-only property67// For deep immutability build a recursive version:8type DeepReadonly<T> = {9  readonly [K in keyof T]: T[K] extends object ? DeepReadonly<T[K]> : T[K];10};1112// Record<K, V>: builds an object type with keys K and values V

Picking and Omitting: Pick and Omit

Pick and Omit derive subtypes from object types. They are indispensable for building DTOs, API response types, and form schemas.

pick-omit.ts
1interface Article {2  id: string;3  title: string;4  body: string;5  authorId: string;6  publishedAt: Date | null;7  tags: string[];8  viewCount: number;9}1011// Pick<T, K>: keep only the listed keys12type ArticleCard = Pick<Article, "id" | "title" | "publishedAt" | "tags">;
Info
Omit is slightly less type-safe than Pick because its second parameter accepts string, not only keyof T. A stricter wrapper: type StrictOmit<T, K extends keyof T> = Omit<T, K>

Union Tools: Exclude, Extract, NonNullable

These three utility types filter and extract union members based on conditions. All three are powered by conditional type distributivity — when a conditional type is applied to a union, it distributes over each member.

exclude-extract-nonnullable.ts
1// Exclude<T, U>: remove union members assignable to U2type Status = "active" | "inactive" | "pending" | "deleted";3type LiveStatus = Exclude<Status, "inactive" | "deleted">;4// "active" | "pending"56// Extract<T, U>: keep only union members assignable to U7type Primitive = string | number | boolean | null | undefined;8type Nullish = Extract<Primitive, null | undefined>;9// null | undefined1011// NonNullable<T>: remove null and undefined12type MaybeUser = User | null | undefined;13type DefiniteUser = NonNullable<MaybeUser>;14// User1516// Internals — distributive conditional types:17type MyExclude<T, U>     = T extends U ? never : T;18type MyExtract<T, U>     = T extends U ? T     : never;19type MyNonNullable<T>    = T extends null | undefined ? never : T;
extract-discriminated-union.ts
1// Practical: filter a discriminated union by tag2type AppEvent =3  | { type: "click"; x: number; y: number }4  | { type: "keydown"; key: string }5  | { type: "keyup"; key: string }6  | { type: "resize"; width: number; height: number };78// Extract all keyboard events9type KeyboardEvent = Extract<AppEvent, { type: "keydown" | "keyup" }>;10// { type: "keydown"; key: string } | { type: "keyup"; key: string }1112// Exclude events that carry a key property13type PointerEvent = Exclude<AppEvent, { key: string }>;14// { type: "click"; ... } | { type: "resize"; ... }

Function Tools: ReturnType, Parameters, ConstructorParameters

These utility types extract type information from function signatures — invaluable when working with third-party functions whose types you do not control.

returntype-parameters.ts
1async function fetchPaginatedUsers(2  page: number,3  limit: number,4  filters?: { role?: string; active?: boolean }5): Promise<{ data: User[]; total: number }> {6  return { data: [], total: 0 };7}89// Infer the return type without writing it manually10type FetchResult = ReturnType<typeof fetchPaginatedUsers>;11// Promise<{ data: User[]; total: number }>12
constructor-parameters.ts
1class HttpClient {2  constructor(3    private readonly baseUrl: string,4    private readonly apiKey: string,5    private readonly timeout = 5_0006  ) {}78  get<T>(path: string): Promise<T> {9    return fetch(`${this.baseUrl}${path}`).then((r) => r.json() as Promise<T>);10  }11}12

Promise Tools: Awaited

Awaited<T> recursively unwraps Promise types, mirroring the actual behaviour of await — including multiple levels of nesting and custom thenables.

awaited.ts
1type A = Awaited<Promise<string>>;           // string2type B = Awaited<Promise<Promise<number>>>; // number (deeply unwrapped)3type C = Awaited<string>;                   // string (non-thenable passthrough)45// Primary use case: derive the resolved type of an async function6async function loadDashboard() {7  const [user, stats, notifications] = await Promise.all([8    fetchUser(),9    fetchStats(),10    fetchNotifications(),11  ]);12  return { user, stats, notifications };

Building Custom Utility Types

The real power arrives when you combine mapped types and conditional types to build utility types tailored to your own domain.

custom-utility-types-1.ts
1// DeepPartial — recursively make all nested properties optional2type DeepPartial<T> = T extends object3  ? { [K in keyof T]?: DeepPartial<T[K]> }4  : T;56interface AppState {7  user: { id: string; profile: { name: string; avatar: string } };8  settings: { theme: "dark" | "light"; language: string };9}1011type PartialState = DeepPartial<AppState>;12// user.profile.avatar is now string | undefined
custom-utility-types-2.ts
1// Paths — generate all valid dot-notation paths of a nested object2type Paths<T, Prefix extends string = ""> = {3  [K in keyof T & string]:4    T[K] extends object5      ? Paths<T[K], `${Prefix}${K}.`> | `${Prefix}${K}`6      : `${Prefix}${K}`;7}[keyof T & string];89interface Config {10  database: { host: string; port: number };11  cache: { ttl: number; prefix: string };12}
Info
When building recursive utility types, always provide an explicit base case to prevent the "Type instantiation is excessively deep" compiler error. TypeScript's default recursion limit is around 100 levels.

Summary

  • Partial and Required toggle property optionality; the -? modifier explicitly removes it
  • Readonly and Record control immutability and object-type shape
  • Pick retains listed keys; Omit removes them — wrap with StrictOmit for stronger type safety
  • Exclude and Extract filter union members via conditional type distributivity
  • ReturnType and Parameters let you work with function signatures without re-typing them
  • Awaited recursively unwraps Promises — compose with ReturnType for async type inference
  • PickByValue and Paths demonstrate custom utility types built with key remapping and template literal types
  • Built: 6/25/2026, 3:03:36 PM