typescript1/20/20247 min read

TypeScript Utility Types

Master TypeScript's built-in utility types for advanced type manipulation

typescriptutility-typestype-manipulationgenerics
By Zen Frontend

TypeScript Utility Types

TypeScript provides a set of built-in utility types that help you transform and manipulate existing types. These utilities are essential for creating flexible and reusable type definitions.

Built-in Utility Types

Partial

Makes all properties of T optional.

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

type PartialUser = Partial<User>;
// Equivalent to:
// {
//   id?: number;
//   name?: string;
//   email?: string;
//   age?: number;
// }

// Usage
function updateUser(id: number, updates: Partial<User>) {
  // Only some properties need to be provided
  return { id, ...updates };
}

updateUser(1, { name: 'John' }); // OK
updateUser(1, { name: 'John', age: 30 }); // OK

Required

Makes all properties of T required.

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

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

// Usage
function createClient(config: RequiredConfig) {
  // All properties are guaranteed to be present
  return {
    url: config.apiUrl,
    timeout: config.timeout,
    retries: config.retries
  };
}

Readonly

Makes all properties of T readonly.

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

type ReadonlyUser = Readonly<MutableUser>;
// Equivalent to:
// {
//   readonly id: number;
//   readonly name: string;
//   readonly email: string;
// }

// Usage
const user: ReadonlyUser = { id: 1, name: 'John', email: 'john@example.com' };
// user.name = 'Jane'; // Error: Cannot assign to 'name' because it is a read-only property

Pick<T, K>

Selects specific properties from T.

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

type UserPublicInfo = Pick<User, 'id' | 'name' | 'email'>;
// Equivalent to:
// {
//   id: number;
//   name: string;
//   email: string;
// }

// Usage
function getUserPublicInfo(user: User): UserPublicInfo {
  return {
    id: user.id,
    name: user.name,
    email: user.email
  };
}

Omit<T, K>

Excludes specific properties from T.

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

type UserWithoutPassword = Omit<User, 'password'>;
// Equivalent to:
// {
//   id: number;
//   name: string;
//   email: string;
//   createdAt: Date;
//   updatedAt: Date;
// }

// Usage
function createUser(userData: Omit<User, 'id' | 'createdAt' | 'updatedAt'>) {
  return {
    ...userData,
    id: Math.random(),
    createdAt: new Date(),
    updatedAt: new Date()
  };
}

Record<K, V>

Creates an object type with keys of type K and values of type V.

type Status = 'pending' | 'approved' | 'rejected';

type StatusMessages = Record<Status, string>;
// Equivalent to:
// {
//   pending: string;
//   approved: string;
//   rejected: string;
// }

const messages: StatusMessages = {
  pending: 'Your request is being processed',
  approved: 'Your request has been approved',
  rejected: 'Your request has been rejected'
};

// Usage with string keys
type UserRoles = Record<string, string[]>;
const userRoles: UserRoles = {
  admin: ['read', 'write', 'delete'],
  user: ['read'],
  guest: []
};

Exclude<T, U>

Excludes types from T that are assignable to U.

type AllColors = 'red' | 'green' | 'blue' | 'yellow' | 'purple';
type PrimaryColors = 'red' | 'green' | 'blue';

type SecondaryColors = Exclude<AllColors, PrimaryColors>;
// Equivalent to: 'yellow' | 'purple'

// Usage
function paintWithSecondary(color: SecondaryColors) {
  // Only 'yellow' or 'purple' are allowed
  console.log(`Painting with ${color}`);
}

Extract<T, U>

Extracts types from T that are assignable to U.

type AllColors = 'red' | 'green' | 'blue' | 'yellow' | 'purple';
type WarmColors = 'red' | 'yellow' | 'orange';

type AvailableWarmColors = Extract<AllColors, WarmColors>;
// Equivalent to: 'red' | 'yellow'

// Usage
function paintWithWarm(color: AvailableWarmColors) {
  // Only 'red' or 'yellow' are allowed
  console.log(`Painting with ${color}`);
}

NonNullable

Excludes null and undefined from T.

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

// Usage
function processString(value: MaybeString): DefiniteString {
  if (value == null) {
    throw new Error('Value cannot be null or undefined');
  }
  return value; // Type is now string
}

Parameters

Extracts the parameter types of a function type T.

function createUser(name: string, age: number, email: string) {
  return { name, age, email };
}

type CreateUserParams = Parameters<typeof createUser>;
// Equivalent to: [string, number, string]

// Usage
function callWithDefaults(fn: typeof createUser, ...args: CreateUserParams) {
  return fn(...args);
}

ReturnType

Extracts the return type of a function type T.

function getUser() {
  return {
    id: 1,
    name: 'John',
    email: 'john@example.com'
  };
}

type User = ReturnType<typeof getUser>;
// Equivalent to:
// {
//   id: number;
//   name: string;
//   email: string;
// }

// Usage
function processUser(user: User) {
  console.log(`Processing user: ${user.name}`);
}

Advanced Utility Types

Custom Utility Types

// Make specific properties optional
type PartialBy<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;

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

type UserRegistration = PartialBy<User, 'id'>;
// Equivalent to:
// {
//   name: string;
//   email: string;
//   password: string;
//   id?: number;
// }

// Make specific properties required
type RequiredBy<T, K extends keyof T> = Omit<T, K> & Required<Pick<T, K>>;

type UserWithRequiredEmail = RequiredBy<User, 'email'>;
// email is now required (though it already was)

// Deep partial
type DeepPartial<T> = {
  [P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P];
};

interface NestedUser {
  id: number;
  profile: {
    name: string;
    avatar: {
      url: string;
      alt: string;
    };
  };
}

type PartialNestedUser = DeepPartial<NestedUser>;
// All nested properties are now optional

Conditional Utility Types

// Extract function return type, handling async functions
type Awaited<T> = T extends Promise<infer U> ? U : T;

type SyncResult = Awaited<string>; // string
type AsyncResult = Awaited<Promise<number>>; // number

// Check if a type is a function
type IsFunction<T> = T extends (...args: any[]) => any ? true : false;

type Test1 = IsFunction<string>; // false
type Test2 = IsFunction<() => void>; // true

// Get the first parameter type
type FirstParameter<T> = T extends (first: infer P, ...args: any[]) => any ? P : never;

type FirstParam = FirstParameter<(name: string, age: number) => void>; // string

Practical Examples

API Response Types

interface ApiResponse<T> {
  data: T;
  status: number;
  message: string;
  timestamp: string;
}

type UserApiResponse = ApiResponse<User>;
type PartialUserApiResponse = ApiResponse<Partial<User>>;

// Extract just the data type
type ApiData<T> = T extends ApiResponse<infer U> ? U : never;
type UserData = ApiData<UserApiResponse>; // User

Form Handling

interface UserForm {
  name: string;
  email: string;
  password: string;
  confirmPassword: string;
}

// Form data without password confirmation
type UserFormData = Omit<UserForm, 'confirmPassword'>;

// Form validation errors
type FormErrors<T> = {
  [K in keyof T]?: string;
};

type UserFormErrors = FormErrors<UserForm>;
// Equivalent to:
// {
//   name?: string;
//   email?: string;
//   password?: string;
//   confirmPassword?: string;
// }

State Management

interface AppState {
  user: User | null;
  loading: boolean;
  error: string | null;
  theme: 'light' | 'dark';
}

// Actions that can be dispatched
type AppAction = 
  | { type: 'SET_USER'; payload: User }
  | { type: 'SET_LOADING'; payload: boolean }
  | { type: 'SET_ERROR'; payload: string | null }
  | { type: 'TOGGLE_THEME' };

// Reducer function type
type AppReducer = (state: AppState, action: AppAction) => AppState;

// Selector function type
type Selector<T> = (state: AppState) => T;
type UserSelector = Selector<User | null>;

Key Concepts

  1. Type Transformation: Utility types transform existing types
  2. Composability: Utility types can be combined and nested
  3. Generic Constraints: Using extends to constrain type parameters
  4. Conditional Types: Using extends and infer for type logic
  5. Template Literal Types: For string manipulation (TypeScript 4.1+)

Common Interview Questions

  • What's the difference between Pick and Omit?
  • How would you create a deep partial type?
  • What's the purpose of Record<K, V>?
  • How do you extract function parameter types?
  • What's the difference between Exclude and Extract?

Related Topics

  • Type vs Interface
  • Generics
  • Conditional Types
  • Template Literal Types
  • Mapped Types