TypeScript Utility Types

TypeScript provides several utility types that help in common type transformations. These built-in utility types save time and improve code quality by enforcing consistent transformations of your existing types.

What Are Utility Types?

Utility types are pre-defined generic types that operate on other types to produce new variations. They allow you to modify types in a standardized way without duplicating type definitions, ensuring your codebase remains DRY (Don't Repeat Yourself).

Essential Utility Types

Partial<Type>

The Partial utility type constructs a type with all properties of the input type set to optional.

Partial utility type

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

// All properties are now optional
type PartialUser = Partial<User>;

// This is valid - we can provide just some properties
const userUpdate: PartialUser = {
  name: "Alice Smith",
  email: "alice.smith@example.com",
};

function updateUser(id: number, userData: Partial<User>) {
  // Update only the provided fields
  // ...
}

This is particularly useful for update operations where only a subset of fields may be modified.

Required<Type>

The Required utility type constructs a type with all properties of the input type set to required, the opposite of Partial.

Required utility type

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

// All properties are now required
type RequiredConfig = Required<Config>;

// Error! Missing properties
// const config: RequiredConfig = { endpoint: "api/users" };

// Valid
const completeConfig: RequiredConfig = {
  endpoint: "api/users",
  timeout: 5000,
  retries: 3,
};

This helps ensure all properties are provided when needed.

Readonly<Type>

The Readonly utility type constructs a type with all properties set to read-only.

Readonly utility type

interface Todo {
  title: string;
  description: string;
  completed: boolean;
}

// All properties are now readonly
type ReadonlyTodo = Readonly<Todo>;

const todo: ReadonlyTodo = {
  title: "Learn TypeScript",
  description: "Study utility types",
  completed: false,
};

// Error! Cannot modify readonly property
// todo.completed = true;

This is useful for creating immutable objects or ensuring values aren't accidentally modified.

Record<Keys, Type>

The Record utility type constructs an object type with a specific set of keys and a defined type for values.

Record utility type

type UserRole = "admin" | "user" | "guest";

// Creates an object with keys from UserRole and values of string
type RoleDescription = Record<UserRole, string>;

const roleDescriptions: RoleDescription = {
  admin: "Full access to all resources",
  user: "Access to own resources",
  guest: "Limited read-only access",
};

// Strongly typed object with specific keys
type APIEndpoints = Record<string, string>;

const endpoints: APIEndpoints = {
  users: "/api/users",
  posts: "/api/posts",
  comments: "/api/comments",
};

Record is excellent for creating dictionaries, lookup tables, or mapping types.

Pick<Type, Keys>

The Pick utility type constructs a type by picking a set of properties from another type.

Pick utility type

interface Article {
  id: number;
  title: string;
  content: string;
  author: string;
  createdAt: Date;
  updatedAt: Date;
  tags: string[];
  published: boolean;
}

// Only include id, title, and author
type ArticlePreview = Pick<Article, "id" | "title" | "author">;

const preview: ArticlePreview = {
  id: 1,
  title: "Understanding TypeScript Utility Types",
  author: "Jane Doe",
  // Other fields are not allowed
};

This helps create more specific subtypes focused only on the properties you need.

Omit<Type, Keys>

The Omit utility type constructs a type by omitting a set of properties from another type. It's the opposite of Pick.

Omit utility type

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

// Exclude sensitive information
type PublicUser = Omit<User, "password" | "email">;

const publicProfile: PublicUser = {
  id: 101,
  username: "johndoe",
  createdAt: new Date(),
};

This is particularly useful for removing sensitive or irrelevant properties from types.

Exclude<Type, ExcludedUnion>

The Exclude utility type constructs a type by excluding all union members that are assignable to ExcludedUnion.

Exclude utility type

type AllEventTypes = "click" | "hover" | "focus" | "blur" | "input" | "change";

// Create a type without mouse events
type NonMouseEvents = Exclude<AllEventTypes, "click" | "hover">;
// Result: "focus" | "blur" | "input" | "change"

function addNonMouseEventListener(event: NonMouseEvents, handler: () => void) {
  // ...
}

// Valid
addNonMouseEventListener("focus", () => console.log("Element focused"));

// Error! 'click' is not assignable to parameter of type NonMouseEvents
// addNonMouseEventListener("click", () => console.log("Element clicked"));

This is helpful for filtering union types to exclude specific values.

Extract<Type, Union>

The Extract utility type constructs a type by extracting all union members from Type that are assignable to Union.

Extract utility type

type FormElements = "input" | "select" | "textarea" | "button" | "label";
type InteractiveElements = "button" | "input" | "a" | "select" | "textarea";

// Extract common elements between the two types
type InteractiveFormElements = Extract<FormElements, InteractiveElements>;
// Result: "input" | "select" | "textarea" | "button"

function setupFormValidation(element: InteractiveFormElements) {
  // ...
}

Extract is useful for finding the common subset between two union types.

NonNullable<Type>

The NonNullable utility type constructs a type by excluding null and undefined from Type.

NonNullable utility type

type Nullable = string | null | undefined;

// Remove null and undefined
type NonNullableString = NonNullable<Nullable>;
// Result: string

function processValue(value: NonNullableString) {
  // No need to check for null or undefined
  return value.toUpperCase();
}

This is helpful when you need to ensure values are neither null nor undefined.

Parameters<Type>

The Parameters utility type extracts the parameter types of a function type as a tuple.

Parameters utility type

function fetchUser(id: number, includeDetails: boolean = false): Promise<User> {
  // ...
  return Promise.resolve({} as User);
}

// Extract parameter types from function
type FetchUserParams = Parameters<typeof fetchUser>;
// Result: [id: number, includeDetails?: boolean | undefined]

// Use it for function composition or mocking
function preprocessFetchUserParams(...args: FetchUserParams) {
  const [id, includeDetails] = args;
  console.log(`Preparing to fetch user ${id} with details: ${includeDetails}`);
  return args;
}

const result = preprocessFetchUserParams(42, true);

This is useful for working with function types without repeating parameter definitions.

ReturnType<Type>

The ReturnType utility type extracts the return type of a function type.

ReturnType utility type

function createUser(name: string, email: string) {
  return {
    id: Date.now(),
    name,
    email,
    createdAt: new Date(),
  };
}

// Extract return type from function
type User = ReturnType<typeof createUser>;

// We can use the extracted type
function updateUserCache(user: User) {
  // ...
}

const newUser = createUser("Alice", "alice@example.com");
updateUserCache(newUser); // Type-safe

This allows you to reuse the return type of a function without explicitly defining it.

InstanceType<Type>

The InstanceType utility type extracts the instance type of a constructor function type.

InstanceType utility type

class ApiClient {
  baseUrl: string;

  constructor(baseUrl: string) {
    this.baseUrl = baseUrl;
  }

  async get(endpoint: string) {
    // ...
  }
}

// Extract the instance type from the class
type ApiClientInstance = InstanceType<typeof ApiClient>;

function configureApiClient(client: ApiClientInstance) {
  // ...
}

const client = new ApiClient("https://api.example.com");
configureApiClient(client); // Type-safe

This is helpful when working with classes and their instance types.

Advanced Utility Types

Awaited<Type>

The Awaited utility type models the await operator in async functions, extracting the type from a Promise.

Awaited utility type

async function fetchUserData(id: number) {
  // ...
  return { id, name: "John", email: "john@example.com" };
}

// Extracts the resolved type from the Promise
type UserData = Awaited<ReturnType<typeof fetchUserData>>;
// Equivalent to: type UserData = { id: number; name: string; email: string; }

function processUserData(userData: UserData) {
  console.log(`Processing user: ${userData.name}`);
}

// Usage in an async context
async function main() {
  const data = await fetchUserData(123);
  processUserData(data); // Type-safe
}

This is particularly useful when working with async functions and Promises.

ThisParameterType<Type>

The ThisParameterType utility type extracts the type of the this parameter from a function type.

ThisParameterType utility type

function formatName(this: { firstName: string; lastName: string }) {
  return `${this.firstName} ${this.lastName}`;
}

type Person = ThisParameterType<typeof formatName>;
// Result: { firstName: string; lastName: string }

const person: Person = {
  firstName: "John",
  lastName: "Doe",
};

const fullName = formatName.call(person); // "John Doe"

This helps when working with functions that use the this context.

OmitThisParameter<Type>

The OmitThisParameter utility type removes the this parameter from a function type.

OmitThisParameter utility type

function greet(this: { name: string }, greeting: string) {
  return `${greeting}, ${this.name}!`;
}

// Remove the 'this' parameter
type GreetFunction = OmitThisParameter<typeof greet>;
// Result: (greeting: string) => string

const boundGreet: GreetFunction = greet.bind({ name: "Alice" });
const result = boundGreet("Hello"); // "Hello, Alice!"

This is useful for creating bound functions with a fixed this context.

ConstructorParameters<Type>

The ConstructorParameters utility type extracts the parameter types of a constructor function type as a tuple.

ConstructorParameters utility type

class Database {
  constructor(host: string, port: number, username: string, password: string) {
    // ...
  }
}

// Extract constructor parameter types
type DatabaseConstructorParams = ConstructorParameters<typeof Database>;
// Result: [host: string, port: number, username: string, password: string]

function createDatabaseFromConfig(config: DatabaseConstructorParams) {
  return new Database(...config);
}

const dbConfig: DatabaseConstructorParams = ["localhost", 5432, "admin", "secret"];

const db = createDatabaseFromConfig(dbConfig);

This is helpful when working with class constructors and their parameter types.

String Manipulation Utility Types

Uppercase<StringType>

Converts each character in a string literal type to uppercase.

Uppercase utility type

type Direction = "north" | "south" | "east" | "west";

type UppercaseDirection = Uppercase<Direction>;
// Result: "NORTH" | "SOUTH" | "EAST" | "WEST"

const direction: UppercaseDirection = "NORTH"; // Valid

Lowercase<StringType>

Converts each character in a string literal type to lowercase.

Lowercase utility type

type EventType = "CLICK" | "SUBMIT" | "FOCUS" | "BLUR";

type LowercaseEventType = Lowercase<EventType>;
// Result: "click" | "submit" | "focus" | "blur"

function addEventListener(event: LowercaseEventType, handler: () => void) {
  // ...
}

addEventListener("click", () => console.log("Clicked")); // Valid

Capitalize<StringType>

Converts the first character in a string literal type to uppercase.

Capitalize utility type

type Status = "pending" | "fulfilled" | "rejected";

type CapitalizedStatus = Capitalize<Status>;
// Result: "Pending" | "Fulfilled" | "Rejected"

function setStatusText(status: CapitalizedStatus) {
  // ...
}

setStatusText("Pending"); // Valid

Uncapitalize<StringType>

Converts the first character in a string literal type to lowercase.

Uncapitalize utility type

type Method = "GET" | "POST" | "PUT" | "DELETE";

type UncapitalizedMethod = Uncapitalize<Method>;
// Result: "gET" | "pOST" | "pUT" | "dELETE"

function logMethod(method: UncapitalizedMethod) {
  // ...
}

logMethod("pOST"); // Valid

Creating Custom Utility Types

While TypeScript provides many built-in utility types, you can also create your own for specific transformations.

Optional Properties with Default Values

Custom utility type for defaults

type WithDefaults<T, D> = {
  [K in keyof T]: K extends keyof D ? T[K] | D[K] : T[K];
};

interface UserSettings {
  theme?: "light" | "dark";
  fontSize?: number;
  notifications?: boolean;
}

const defaultSettings = {
  theme: "light",
  fontSize: 16,
  notifications: true,
};

type UserSettingsWithDefaults = WithDefaults<UserSettings, typeof defaultSettings>;

function applySettings(settings: Partial<UserSettings>) {
  const finalSettings: UserSettingsWithDefaults = {
    ...defaultSettings,
    ...settings,
  };

  // Now we can safely use properties with their default values
  console.log(`Theme: ${finalSettings.theme}`);
  console.log(`Font Size: ${finalSettings.fontSize}px`);
  console.log(`Notifications: ${finalSettings.notifications}`);
}

Improved Pick with Deep Path Selection

Deep path pick utility

type Path<T, P extends string> = P extends `${infer K}.${infer R}`
  ? K extends keyof T
    ? { [key in K]: Path<T[K], R> }
    : never
  : P extends keyof T
    ? { [key in P]: T[P] }
    : never;

interface User {
  id: number;
  profile: {
    name: string;
    contact: {
      email: string;
      phone: string;
    };
  };
  settings: {
    theme: "light" | "dark";
    notifications: boolean;
  };
}

// Pick nested properties with dot notation
type UserEmail = Path<User, "profile.contact.email">;
// Result: { profile: { contact: { email: string } } }

function getUserEmail(user: User): string {
  const emailObj: UserEmail = {
    profile: {
      contact: {
        email: user.profile.contact.email,
      },
    },
  };

  return emailObj.profile.contact.email;
}

Mutable Utility Type

Custom mutable utility type

type Mutable<T> = {
  -readonly [P in keyof T]: T[P];
};

interface ReadonlyUser {
  readonly id: number;
  readonly name: string;
  readonly email: string;
}

type MutableUser = Mutable<ReadonlyUser>;
// Result: { id: number; name: string; email: string; }

function updateUserData(user: MutableUser) {
  user.name = "Updated Name"; // Valid
  user.email = "updated@example.com"; // Valid
}

Combining Utility Types

Utility types can be combined to create more complex type transformations.

Combining utility types

interface User {
  id: string;
  name: string;
  email: string;
  password: string;
  settings: {
    theme: string;
    notifications: boolean;
  };
  roles: string[];
  createdAt: Date;
}

// Create a type for user creation (no id or createdAt)
type CreateUserData = Omit<User, "id" | "createdAt">;

// Create a type for public user data (no password)
type PublicUserData = Omit<User, "password">;

// Create a type for updating user settings only
type UserSettingsUpdate = Partial<User["settings"]>;

// Create a type for a user with specific roles
type AdminUser = User & { roles: ["admin", ...string[]] };

// Create a type for a minimal user display
type UserSummary = Pick<User, "id" | "name"> & { joined: string };

function formatUser(user: User): UserSummary {
  return {
    id: user.id,
    name: user.name,
    joined: user.createdAt.toLocaleDateString(),
  };
}

Practical Examples

API Response Handling

API response handling

// Define base entity type
interface Entity {
  id: string;
  createdAt: string;
  updatedAt: string;
}

// Define specific entity types
interface User extends Entity {
  name: string;
  email: string;
  isActive: boolean;
}

interface Post extends Entity {
  title: string;
  content: string;
  authorId: string;
  isPublished: boolean;
}

// API response wrapper type
interface ApiResponse<T> {
  data: T;
  meta: {
    status: number;
    message: string;
  };
}

// Create types for different API operations
type CreateUserRequest = Omit<User, keyof Entity | "isActive">;
type UpdateUserRequest = Partial<Omit<User, keyof Entity>>;
type UserResponse = ApiResponse<User>;

// Function to create a user
async function createUser(userData: CreateUserRequest): Promise<UserResponse> {
  const response = await fetch("/api/users", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify(userData),
  });

  return response.json();
}

Form State Management

Form state management

interface FormField<T> {
  value: T;
  touched: boolean;
  error?: string;
  required: boolean;
}

// Create form state type from a data model
type FormState<T> = {
  [K in keyof T]: FormField<T[K]>;
};

// Create a validator results type
type ValidationResults<T> = {
  [K in keyof T]?: string;
};

interface UserData {
  username: string;
  email: string;
  password: string;
  age: number;
}

// Create a type for the user form state
type UserFormState = FormState<UserData>;

// Initialize form state
function initializeForm<T>(initialData: Partial<T>, requiredFields: Array<keyof T>): FormState<T> {
  // Implementation omitted for brevity
  return {} as FormState<T>;
}

// Validate form with type-safe validators
type ValidatorFn<T> = (value: T) => string | undefined;
type Validators<T> = {
  [K in keyof T]: ValidatorFn<T[K]>;
};

// Usage example
const validators: Validators<UserData> = {
  username: (value) => (value.length < 3 ? "Username must be at least 3 characters" : undefined),
  email: (value) => (!value.includes("@") ? "Email must be valid" : undefined),
  password: (value) => (value.length < 8 ? "Password must be at least 8 characters" : undefined),
  age: (value) => (value < 18 ? "You must be at least 18 years old" : undefined),
};

Best Practices for Using Utility Types

  1. Choose the right utility type for the task: Understand what each utility type does and select the one that best fits your needs.

Choosing the right utility

// DON'T: Creating a new type that duplicates Partial
type OptionalUser = {
  id?: number;
  name?: string;
  email?: string;
};

// DO: Use the built-in Partial utility
type OptionalUser = Partial<User>;
  1. Compose utility types for complex transformations: Combine multiple utility types for more specific type transformations.

Composing utility types

// Create a type for updating a user without changing sensitive fields
type SafeUserUpdate = Partial<Omit<User, "id" | "role" | "password">>;

// Create a read-only view of specific user fields
type UserSummary = Readonly<Pick<User, "id" | "name" | "email">>;
  1. Create custom utility types for repeated patterns: If you find yourself performing the same type transformations repeatedly, create a custom utility type.

Custom utility types

// Custom utility type for API request parameters
type RequestParams<T> = {
  [key: string]: string | number | boolean;
  include?: Array<keyof T>;
  fields?: Array<keyof T>;
};

// Usage
function fetchUsers(params: RequestParams<User>) {
  // ...
}
  1. Document complex utility types: When combining or creating utility types, add comments to explain what they represent.

Documenting utility types

/**
 * Represents a user record with only public fields,
 * made read-only to prevent accidental modification.
 */
type PublicUserProfile = Readonly<Omit<User, "password" | "email">>;

/**
 * Represents an API payload for updating partial user data,
 * excluding system-managed fields.
 */
type UserUpdatePayload = Partial<Omit<User, "id" | "createdAt" | "updatedAt">>;
  1. Use mapped types for more dynamic transformations: For complex transformations, combine utility types with mapped type modifiers.

Advanced mapped types

// Make all string properties optional and all number properties readonly
type SpecialTransform<T> = {
  readonly [K in keyof T as T[K] extends number ? K : never]: T[K];
} & {
  [K in keyof T as T[K] extends string ? K : never]?: T[K];
};

interface Product {
  id: number;
  sku: number;
  name: string;
  description: string;
  price: number;
  category: string;
}

// Result: id, sku, price are readonly; name, description, category are optional
type TransformedProduct = SpecialTransform<Product>;

Summary

TypeScript utility types provide powerful tools for type transformations without repeating type definitions:

  • Structural Utilities: Partial<T>, Required<T>, Readonly<T>, Record<K,T>, Pick<T,K>, and Omit<T,K> modify the structure of your types
  • Union and Intersection Utilities: Exclude<T,U>, Extract<T,U>, and NonNullable<T> create new types from existing ones
  • Function and Class Utilities: Parameters<T>, ReturnType<T>, ConstructorParameters<T>, and InstanceType<T> extract types from functions and classes
  • String Manipulation Utilities: Uppercase<T>, Lowercase<T>, Capitalize<T>, and Uncapitalize<T> transform string literal types
  • Custom Utility Types: Create your own utility types for domain-specific transformations

Using utility types effectively makes your TypeScript code more maintainable, type-safe, and DRY. Remember to avoid any type and use unknown with proper type guards when dealing with data of uncertain types. When possible, combine multiple utility types to create precise type definitions that match your exact needs.

For complex data transformations, especially when working with external APIs or form input validation, utility types provide standardized patterns that ensure strong typing throughout your application.