Skip to content
⚠️ This article was written in 2019. Some content may be outdated.

TypeScript Utility Types In Depth

TypeScript ships with a collection of utility types. Most developers know Partial and Pick, but the full library is much richer and more powerful than that.

Built-in Utility Types

typescript
interface User {
  id: number;
  name: string;
  email: string;
  age: number;
  role: "admin" | "user" | "guest";
}

// Partial<T> — all properties become optional
type PartialUser = Partial<User>;
// { id?: number; name?: string; email?: string; ... }

// Required<T> — all properties become required (opposite of Partial)
type RequiredUser = Required<PartialUser>;

// Pick<T, K> — select a subset of properties
type UserProfile = Pick<User, "name" | "email">;
// { name: string; email: string }

// Omit<T, K> — exclude properties
type UserWithoutId = Omit<User, "id">;
// { name: string; email: string; age: number; role: ... }

// Record<K, V> — map type: key → value
type RoleMap = Record<User["role"], string[]>;
// { admin: string[]; user: string[]; guest: string[] }

// Readonly<T> — all properties become readonly
type ReadonlyUser = Readonly<User>;
// Cannot modify any property after assignment

// NonNullable<T> — remove null and undefined
type MaybeUser = User | null | undefined;
type DefiniteUser = NonNullable<MaybeUser>; // User

// ReturnType<T> — extract a function's return type
function fetchUser(): Promise<User> {
  /* ... */
}
type FetchResult = ReturnType<typeof fetchUser>; // Promise<User>

// Parameters<T> — extract a function's parameter types as a tuple
type FetchParams = Parameters<typeof fetchUser>; // []

Conditional Types and infer

typescript
// infer: extract a type from within a generic during conditional type matching
type UnpackPromise<T> = T extends Promise<infer U> ? U : T;
// UnpackPromise<Promise<string>> → string
// UnpackPromise<number>         → number

// Extract the element type from an array
type UnpackArray<T> = T extends Array<infer Item> ? Item : T;
// UnpackArray<string[]> → string

// Extract the return type of an async function
type AsyncReturnType<T extends (...args: any) => Promise<any>> = T extends (
  ...args: any
) => Promise<infer R>
  ? R
  : never;

async function getUser(): Promise<User> {
  /* ... */
}
type UserType = AsyncReturnType<typeof getUser>; // User

Custom DeepPartial

Built-in Partial is only one level deep. For nested objects, use a recursive version:

typescript
type DeepPartial<T> = {
  [K in keyof T]?: T[K] extends object ? DeepPartial<T[K]> : T[K];
};

interface Config {
  server: {
    host: string;
    port: number;
    ssl: {
      enabled: boolean;
      cert: string;
    };
  };
  database: {
    url: string;
    poolSize: number;
  };
}

// Deep partial — all nested properties are optional too
type PartialConfig = DeepPartial<Config>;
const config: PartialConfig = {
  server: {
    host: "localhost",
    // port, ssl can be omitted
  },
  // database can be omitted entirely
};

TypeScript's utility type system is one of its most powerful features. Invest time in learning the built-in utilities before writing your own — they cover the majority of real-world scenarios.

MIT Licensed