Skip to main content
TypeScript intermediate Lesson 10 of 21

Utility Types in TypeScript

Master TypeScript's built-in utility types: Partial, Required, Readonly, Pick, Omit, Record, ReturnType, Awaited, and more.

Partial

Partial<T> makes all properties of T optional by adding ? to every field. This is extremely useful for update and patch operations where you only want to send the fields that changed — without it, you’d need a separate hand-written type for every partial update.

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

type UserUpdate = Partial<User>;
// { id?: number; name?: string; email?: string; role?: string }

async function updateUser(id: number, changes: Partial<User>): Promise<User> {
  const existing = await findUser(id);
  return { ...existing, ...changes }; // merge only the provided fields
}

updateUser(1, { name: "New Name" });           // fine — only updating name
updateUser(1, { name: "New", role: "admin" }); // fine — updating two fields

Required

Required<T> is the inverse of Partial — it removes ? from every property, making all fields mandatory. It’s most useful after a validation step, where you can assert that all optional config fields have been filled in with defaults.

interface Config {
  apiUrl?: string;
  timeout?: number;
  retries?: number;
}

type FullConfig = Required<Config>;
// { apiUrl: string; timeout: number; retries: number }

// After this function, callers can treat all fields as definitely present
function validateConfig(raw: Config): FullConfig {
  if (!raw.apiUrl) throw new Error("apiUrl required");
  return {
    apiUrl: raw.apiUrl,
    timeout: raw.timeout ?? 5000,
    retries: raw.retries ?? 3,
  };
}

Readonly

Readonly<T> adds the readonly modifier to every property, preventing mutation after the object is created. This is useful for configuration objects, function parameters you don’t want to accidentally modify, and immutable value types.

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

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

// Particularly useful for function parameters — signals you won't mutate the input
function translate(point: Readonly<Point>, dx: number, dy: number): Point {
  // point.x += dx; // Error — would mutate the input
  return { x: point.x + dx, y: point.y + dy }; // return a new point instead
}

Note: Readonly is shallow. Nested objects remain mutable — for deep immutability you need a recursive mapped type.

Pick

Pick<T, K> creates a new type containing only the properties you specify. This is the right tool when you need a subset of a larger type — for example, a public API response that omits sensitive fields, or a form that only edits certain properties.

interface User {
  id: number;
  name: string;
  email: string;
  password: string;
  role: string;
  createdAt: Date;
}

// Public profile — expose only safe fields, never password or timestamps
type PublicUser = Pick<User, "id" | "name" | "role">;
// { id: number; name: string; role: string }

// Login form — only needs these two fields
type LoginCredentials = Pick<User, "email" | "password">;

function getPublicProfile(user: User): PublicUser {
  const { id, name, role } = user;
  return { id, name, role }; // TypeScript verifies this matches PublicUser
}

Omit

Omit<T, K> creates a type with the specified keys removed — the inverse of Pick. Use it when it’s easier to list the fields you want to exclude rather than the ones you want to keep. A common pattern is omitting server-generated fields from creation payloads.

// Creation payload — omit server-generated fields the client shouldn't provide
type CreateUserDto = Omit<User, "id" | "createdAt">;

// Safe user response — never send the password to the client
type SafeUser = Omit<User, "password">;

async function createUser(data: CreateUserDto): Promise<User> {
  const id = generateId();
  const createdAt = new Date();
  return { ...data, id, createdAt }; // server fills in the omitted fields
}

Record

Record<K, V> creates an object type where all keys are of type K and all values are of type V. It’s more expressive than a plain index signature because it can accept a union type as K, which means TypeScript verifies every key in the union is present.

// A string dictionary — any string key, string value
type StringMap = Record<string, string>;

// Exhaustive map — every role must have an entry
type RolePermissions = Record<"admin" | "editor" | "viewer", string[]>;

const permissions: RolePermissions = {
  admin: ["read", "write", "delete"],
  editor: ["read", "write"],
  viewer: ["read"],
  // Omitting any role would be a TypeScript error
};

// Mapping status values to display config
type StatusConfig = Record<
  "idle" | "loading" | "success" | "error",
  { label: string; color: string }
>;

const statusConfig: StatusConfig = {
  idle:    { label: "Idle",       color: "gray"  },
  loading: { label: "Loading...", color: "blue"  },
  success: { label: "Done",       color: "green" },
  error:   { label: "Failed",     color: "red"   },
};

Exclude and Extract

Exclude<T, U> removes members from a union type — it filters out any member of T that is assignable to U. This is useful for narrowing a union after you’ve handled certain cases.

type Status = "active" | "inactive" | "banned" | "deleted";

// Remove the moderated statuses — only regular lifecycle statuses remain
type ActiveStatus = Exclude<Status, "banned" | "deleted">;
// "active" | "inactive"

type Primitive = string | number | boolean | null | undefined;
type NonNullPrimitive = Exclude<Primitive, null | undefined>;
// string | number | boolean

Extract<T, U> is the opposite — it keeps only the members of T that are assignable to U:

// Keep only strings from the union
type Strings = Extract<string | number | boolean, string>;
// string

type Shapes = "circle" | "square" | "triangle" | "polygon";
type Simple = Extract<Shapes, "circle" | "square">;
// "circle" | "square"

NonNullable

NonNullable<T> removes null and undefined from a type. It’s the typed equivalent of asserting “I’ve already checked this isn’t null.” Use it after a null check to get a cleaner type for the remainder of a function.

type MaybeString = string | null | undefined;
type DefiniteString = NonNullable<MaybeString>;
// string

// Useful for assertion helpers that throw instead of returning null
function assertDefined<T>(value: T | null | undefined): NonNullable<T> {
  if (value == null) throw new Error("Expected a value, got null or undefined");
  return value as NonNullable<T>;
}

ReturnType

ReturnType<T> extracts the return type of a function type. It’s invaluable when you need to type a variable that holds the result of a function but don’t want to repeat the return type manually — especially when the function’s return type is complex or inferred.

function getUser() {
  return { id: 1, name: "Alice", email: "alice@example.com" };
}

// Derive the type from the function — stays in sync automatically if the function changes
type User = ReturnType<typeof getUser>;
// { id: number; name: string; email: string }

// Useful for factory functions that return complex objects
function createStore() {
  let count = 0;
  return {
    increment() { count++; },
    decrement() { count--; },
    getCount() { return count; },
  };
}

type Store = ReturnType<typeof createStore>;
// { increment: () => void; decrement: () => void; getCount: () => number }

Parameters

Parameters<T> extracts the parameter types of a function type as a tuple. It’s useful when you need to capture, forward, or partially apply arguments while keeping them typed.

function connect(host: string, port: number, secure: boolean): void {}

type ConnectArgs = Parameters<typeof connect>;
// [host: string, port: number, secure: boolean]

// Capture and spread as arguments later
const args: ConnectArgs = ["localhost", 3000, false];
connect(...args); // fully typed — TypeScript verifies the spread matches the signature

Awaited

Awaited<T> unwraps Promise types recursively, giving you the value type that a Promise resolves to. It handles nested Promises and non-Promise values gracefully — making it useful for typing the result of async operations.

type A = Awaited<Promise<string>>;             // string
type B = Awaited<Promise<Promise<number>>>;    // number — unwraps both levels
type C = Awaited<string>;                      // string — non-promise passes through

async function fetchUser(): Promise<User> { /* ... */ }

// Derive the resolved type without unwrapping Promise manually
type FetchedUser = Awaited<ReturnType<typeof fetchUser>>;
// User

InstanceType

InstanceType<T> extracts the instance type produced by a class constructor. It’s useful in factory patterns and dependency injection where you work with constructor references rather than instances directly.

class DatabaseConnection {
  query(sql: string): Promise<unknown[]> { return Promise.resolve([]); }
  close(): void {}
}

type DBConn = InstanceType<typeof DatabaseConnection>;
// DatabaseConnection — same as using the class name as a type, but works generically

// A factory that accepts any constructor and types the instance correctly
function withConnection<T>(
  Ctor: new () => T,
  fn: (conn: T) => Promise<void>
): Promise<void> {
  const conn = new Ctor();
  return fn(conn);
}

Combining Utility Types

The real power of utility types comes from composing them. You can derive an entire family of related types from a single source-of-truth interface, keeping everything in sync as the base type evolves.

interface User {
  id: number;
  name: string;
  email: string;
  password: string;
  role: "admin" | "user";
  createdAt: Date;
  updatedAt: Date;
}

// Create: omit server-generated fields — client provides the rest
type CreateUser = Omit<User, "id" | "createdAt" | "updatedAt">;

// Update: all fields optional, server fields still excluded
type UpdateUser = Partial<Omit<User, "id" | "createdAt" | "updatedAt">>;

// Response: never expose password, make it readonly so callers don't mutate it
type UserResponse = Readonly<Omit<User, "password">>;

// List item: minimal data for table rows — only what the UI needs
type UserListItem = Pick<User, "id" | "name" | "email" | "role">;

This pattern — deriving several types from a single source of truth — is one of the most practical applications of TypeScript utility types in production codebases. When the User interface changes, all derived types update automatically.

Frequently Asked Questions

Do I need to import utility types?
No. Utility types like Partial, Required, Pick, Omit, and Record are built into TypeScript's standard library. They are available in any TypeScript file without imports.
What is the difference between Pick and Omit?
Pick<T, K> keeps only the specified keys K from type T. Omit<T, K> keeps everything except the specified keys K. Use Pick when you want a small subset; use Omit when it is easier to list what to exclude.
When should I use Partial vs Required?
Use Partial<T> when creating update/patch payloads where not all fields are required. Use Required<T> when you need to ensure all optional fields are present — for example, after validating user input.