TypeScript Utility Types: The Complete Guide

Master TypeScript's built-in utility types and learn to create your own. Covers Partial, Pick, Omit, Record, and advanced custom types.

Alex Chen
Alex Chen
December 10, 2024 14 min read
TypeScript Utility Types: The Complete Guide

TypeScript’s utility types are powerful tools that transform existing types into new ones. They reduce boilerplate, improve type safety, and make your code more expressive. Let’s explore every built-in utility type and learn to create custom ones.

Partial and Required

Partial<T>

Makes all properties optional:

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

// All properties become optional
type PartialUser = Partial<User>;
// Equivalent to:
// {
//   id?: number;
//   name?: string;
//   email?: string;
//   avatar?: string;
// }

// Perfect for update functions
function updateUser(id: number, updates: Partial<User>): User {
  const user = getUserById(id);
  return { ...user, ...updates };
}

updateUser(1, { name: 'New Name' }); // Only update name

Required<T>

Makes all properties required (opposite of Partial):

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

type RequiredConfig = Required<Config>;
// All properties now required:
// {
//   apiKey: string;
//   timeout: number;
//   retries: number;
// }

function initializeApp(config: RequiredConfig): void {
  // All values guaranteed to exist
  console.log(config.apiKey, config.timeout, config.retries);
}

Pick and Omit

Pick<T, K>

Creates a type with only selected properties:

interface Article {
  id: number;
  title: string;
  content: string;
  author: string;
  publishedAt: Date;
  views: number;
}

// Only pick what you need
type ArticlePreview = Pick<Article, 'id' | 'title' | 'author'>;
// {
//   id: number;
//   title: string;
//   author: string;
// }

function renderPreview(article: ArticlePreview): JSX.Element {
  return (
    <div>
      <h2>{article.title}</h2>
      <span>By {article.author}</span>
    </div>
  );
}

Omit<T, K>

Creates a type excluding selected properties:

// Remove sensitive fields
type PublicUser = Omit<User, 'password' | 'apiKey' | 'internalNotes'>;

// For API responses
type UserResponse = Omit<User, 'password'>;

// For creation (omit auto-generated fields)
type CreateUserInput = Omit<User, 'id' | 'createdAt' | 'updatedAt'>;

Record and Readonly

Record<K, V>

Creates an object type with specific key and value types:

// String keys with User values
type UserMap = Record<string, User>;

const users: UserMap = {
  'user-1': { id: 1, name: 'Alice', email: 'alice@example.com' },
  'user-2': { id: 2, name: 'Bob', email: 'bob@example.com' },
};

// Literal union keys
type Role = 'admin' | 'editor' | 'viewer';
type Permissions = Record<Role, string[]>;

const permissions: Permissions = {
  admin: ['read', 'write', 'delete', 'manage-users'],
  editor: ['read', 'write'],
  viewer: ['read'],
};

// Status colors
type Status = 'success' | 'warning' | 'error';
type StatusColors = Record<Status, string>;

const colors: StatusColors = {
  success: '#10B981',
  warning: '#F59E0B',
  error: '#EF4444',
};

Readonly<T>

Makes all properties read-only:

interface State {
  user: User | null;
  isLoading: boolean;
  error: string | null;
}

type ReadonlyState = Readonly<State>;

const state: ReadonlyState = {
  user: null,
  isLoading: false,
  error: null,
};

// state.isLoading = true; // Error: Cannot assign to 'isLoading'

Extract and Exclude

Extract<T, U>

Extracts types from a union that are assignable to U:

type AllEvents =
  | { type: 'click'; x: number; y: number }
  | { type: 'scroll'; offset: number }
  | { type: 'keydown'; key: string }
  | { type: 'focus' }
  | { type: 'blur' };

// Extract events with coordinates
type MouseEvents = Extract<AllEvents, { x: number }>;
// { type: 'click'; x: number; y: number }

// Extract by type literal
type ClickEvent = Extract<AllEvents, { type: 'click' }>;

Exclude<T, U>

Removes types from a union:

type Primitive = string | number | boolean | null | undefined;

// Remove null and undefined
type NonNullablePrimitive = Exclude<Primitive, null | undefined>;
// string | number | boolean

// Filter event types
type NonMouseEvents = Exclude<AllEvents, { x: number }>;
// All events except click

NonNullable and ReturnType

NonNullable<T>

Removes null and undefined from a type:

type MaybeString = string | null | undefined;

type DefiniteString = NonNullable<MaybeString>;
// string

function processValue(value: MaybeString): void {
  // value could be null or undefined here

  if (value != null) {
    // TypeScript knows value is string here
    const definite: NonNullable<typeof value> = value;
    console.log(definite.toUpperCase());
  }
}

ReturnType<T>

Extracts the return type of a function:

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

type CreatedUser = ReturnType<typeof createUser>;
// {
//   id: number;
//   name: string;
//   email: string;
//   createdAt: Date;
// }

// Useful for async functions too
async function fetchUsers(): Promise<User[]> {
  // ...
}

type FetchUsersResult = Awaited<ReturnType<typeof fetchUsers>>;
// User[]

Parameters and ConstructorParameters

Parameters<T>

Extracts function parameter types as a tuple:

function greet(name: string, age: number, isVip?: boolean): string {
  return `Hello, ${name}!`;
}

type GreetParams = Parameters<typeof greet>;
// [name: string, age: number, isVip?: boolean]

// Use with spread
function logAndGreet(...args: Parameters<typeof greet>): void {
  console.log('Calling greet with:', args);
  greet(...args);
}

ConstructorParameters<T>

Extracts constructor parameter types:

class ApiClient {
  constructor(
    private baseUrl: string,
    private apiKey: string,
    private timeout: number = 5000
  ) {}
}

type ApiClientParams = ConstructorParameters<typeof ApiClient>;
// [baseUrl: string, apiKey: string, timeout?: number]

function createApiClient(...args: ApiClientParams): ApiClient {
  return new ApiClient(...args);
}

Creating Custom Utility Types

DeepPartial

Make all nested properties optional:

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

interface NestedConfig {
  database: {
    host: string;
    port: number;
    credentials: {
      username: string;
      password: string;
    };
  };
  cache: {
    enabled: boolean;
    ttl: number;
  };
}

type PartialConfig = DeepPartial<NestedConfig>;

const config: PartialConfig = {
  database: {
    host: 'localhost',
    // port, credentials all optional
  },
  // cache entirely optional
};

DeepReadonly

Make all nested properties readonly:

type DeepReadonly<T> = {
  readonly [P in keyof T]: T[P] extends object
    ? DeepReadonly<T[P]>
    : T[P];
};

const frozenConfig: DeepReadonly<NestedConfig> = {
  database: {
    host: 'localhost',
    port: 5432,
    credentials: {
      username: 'admin',
      password: 'secret',
    },
  },
  cache: {
    enabled: true,
    ttl: 3600,
  },
};

// frozenConfig.database.port = 3306; // Error!
// frozenConfig.database.credentials.password = 'new'; // Error!

Mutable

Remove readonly from all properties:

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

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

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

RequireAtLeastOne

Require at least one property from a set:

type RequireAtLeastOne<T, Keys extends keyof T = keyof T> =
  Pick<T, Exclude<keyof T, Keys>> &
  {
    [K in Keys]-?: Required<Pick<T, K>> & Partial<Pick<T, Exclude<Keys, K>>>;
  }[Keys];

interface SearchParams {
  query?: string;
  category?: string;
  tags?: string[];
}

type ValidSearch = RequireAtLeastOne<SearchParams>;

// const search: ValidSearch = {}; // Error!
const search: ValidSearch = { query: 'typescript' }; // OK

Branded Types

Create nominal types for type safety:

type Brand<T, B> = T & { __brand: B };

type UserId = Brand<string, 'UserId'>;
type OrderId = Brand<string, 'OrderId'>;

function getUser(id: UserId): User {
  // ...
}

function getOrder(id: OrderId): Order {
  // ...
}

const userId = 'user-123' as UserId;
const orderId = 'order-456' as OrderId;

getUser(userId); // OK
// getUser(orderId); // Error! Type mismatch

Practical Examples

API Response Types

// Base response
interface ApiResponse<T> {
  data: T;
  status: number;
  message: string;
}

// Paginated response
interface PaginatedResponse<T> extends ApiResponse<T[]> {
  pagination: {
    page: number;
    perPage: number;
    total: number;
    totalPages: number;
  };
}

// Error response
type ErrorResponse = Omit<ApiResponse<null>, 'data'> & {
  error: {
    code: string;
    details?: Record<string, string[]>;
  };
};

Form State Types

interface FormField<T> {
  value: T;
  error: string | null;
  touched: boolean;
  dirty: boolean;
}

type FormState<T> = {
  [K in keyof T]: FormField<T[K]>;
};

interface LoginForm {
  email: string;
  password: string;
  rememberMe: boolean;
}

type LoginFormState = FormState<LoginForm>;

const form: LoginFormState = {
  email: { value: '', error: null, touched: false, dirty: false },
  password: { value: '', error: null, touched: false, dirty: false },
  rememberMe: { value: false, error: null, touched: false, dirty: false },
};

Conclusion

TypeScript utility types are essential for:

  • Reducing boilerplate: Transform types instead of redefining
  • Type safety: Catch errors at compile time
  • Expressiveness: Clearly communicate intent
  • Maintainability: Single source of truth for types

Master these utilities and you’ll write more robust TypeScript code with less effort.


What’s your favorite TypeScript utility type? Share in the comments!

Advertisement

In-Article Ad

Dev Mode

Share this article

Alex Chen

Alex Chen

Senior Full-Stack Developer

I'm a passionate full-stack developer with 10+ years of experience building scalable web applications. I write about Vue.js, Node.js, PostgreSQL, and modern DevOps practices.

Enjoyed this article?

Subscribe to get more tech content delivered to your inbox.

Related Articles