Skip to main content

Command Palette

Search for a command to run...

TypeScript Utility Types: A Complete Guide

Updated
3 min readView as Markdown
TypeScript Utility Types: A Complete Guide

cover

TypeScript's built-in utility types are powerful tools that help you write cleaner, more maintainable code. In this guide, I'll walk through the most commonly used utility types with practical examples from real-world applications.

', description: 'Perfect for update flows where callers only send the fields they want to change.', bullets: ['Form patches', 'Update DTOs', 'Feature-flagged config overrides'], tone: 'success' }, { eyebrow: 'STRICTNESS', title: 'Required', description: 'Useful when a loose input shape becomes a guaranteed runtime shape after defaults or validation.', bullets: ['Normalized config', 'Post-validation objects', 'Internal invariants'], tone: 'info' }, { eyebrow: 'SHAPING', title: 'PickandOmit', description: 'The fastest way to carve focused view models and payload types out of larger domain types.', bullets: ['Preview cards', 'Create/update payloads', 'Public vs internal fields'], tone: 'warning' }, { eyebrow: 'MAPS', title: 'Record', description: 'Great for dictionaries, lookup tables, and keyed collections where the value shape is consistent.', bullets: ['Role maps', 'Route registries', 'Status-to-label maps'], tone: 'violet' }, { eyebrow: 'SAFETY', title: 'ReadonlyandNonNullable', description: 'These types help lock down accidental mutation and strip nullable cases after checks or normalization.', bullets: ['Immutable config objects', 'Derived non-null props', 'Safer shared state'], tone: 'success' }, { eyebrow: 'INFERENCE', title: 'ReturnTypeandAwaited`', description: 'Use them to extract shapes from real functions instead of manually duplicating types that will drift.', bullets: ['Async loader results', 'Factory output types', 'API wrapper return values'], tone: 'info' } ]} />

Why Utility Types Matter

When building large-scale applications like the ones I work on at Expedia Group, type safety isn't just nice to have — it's essential. Utility types help you derive new types from existing ones without duplication.

Partial<T>

Makes all properties of T optional. This is incredibly useful for update functions.

interface User {
  id: string;
  name: string;
  email: string;
  role: 'admin' | 'user';
}

function updateUser(id: string, updates: Partial) {
  // Only update the fields that were provided
}

updateUser('123', { name: 'Umesh' }); // Valid!

Required<T>

The opposite of Partial — makes all properties required.

interface Config {
  host?: string;
  port?: number;
  debug?: boolean;
}

const defaultConfig: Required = {
  host: 'localhost',
  port: 3000,
  debug: false,
};

Pick<T, K>

Creates a type with only the specified properties.

type UserPreview = Pick;

// Equivalent to:
// { id: string; name: string }

Omit<T, K>

Creates a type excluding the specified properties.

type CreateUserInput = Omit;

// Everything except id

Record<K, T>

Creates a type with keys of type K and values of type T.

type UserRoles = Record<string, User[]>;

const roleMap: UserRoles = {
  admin: [/* admin users */],
  editor: [/* editor users */],
};

Practical Example: API Response Types

Here's how I combine these utility types in real projects:

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

type UserListResponse = ApiResponse[]>;
type UserUpdatePayload = Partial>;

A Few More Utility Types Worth Knowing

Readonly<T>

Use Readonly when an object should never be mutated after creation.

interface FeatureFlags {
  newSearch: boolean;
  redesignedCheckout: boolean;
}

const flags: Readonly = {
  newSearch: true,
  redesignedCheckout: false,
};

NonNullable<T>

Strip null and undefined once you've validated a value.

type MaybeUser = User | null | undefined;
type SafeUser = NonNullable;

ReturnType<T> and Awaited<T>

Infer the result shape from real functions instead of duplicating it by hand.

async function fetchCurrentUser() {
  return { id: '123', name: 'Umesh', role: 'admin' as const };
}

type FetchCurrentUserResult = Awaited>;

Key Takeaways

  • Use Partial for update operations where not all fields are required
  • Use Pick and Omit to create focused types from larger interfaces
  • Use Record for dictionary-like structures
  • Readonly, NonNullable, ReturnType, and Awaited cover a lot of everyday type work
  • Combine utility types for complex transformations
  • These types are zero-cost at runtime — they only exist during compilation

Mastering these utility types will significantly improve your TypeScript code quality and developer experience.


Originally published at umesh-malik.com

More from this blog

Scalable software systems By Umesh Malik

27 posts