Skip to content

Utility Types#

TypeScript provides powerful built-in utility types that help you transform and manipulate types. These are essential for writing clean, DRY (Don't Repeat Yourself) code.

Essential Utility Types#

Partial<T>#

Makes all properties of T optional.

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

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

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

updateUser(1, { name: "Alice" });           // ✅ OK
updateUser(1, { email: "alice@example.com" }); // ✅ OK
updateUser(1, { name: "Alice", age: 26 });  // ✅ OK

Required<T>#

Makes all properties of T required.

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

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

function initializeApp(config: RequiredConfig): void {
  // All properties are guaranteed to exist
  console.log(config.apiUrl);  // No need for optional chaining
  console.log(config.timeout);
  console.log(config.retries);
}

Readonly<T>#

Makes all properties of T readonly.

interface Settings {
  theme: string;
  language: string;
}

type ReadonlySettings = Readonly<Settings>;
// { readonly theme: string; readonly language: string; }

const settings: ReadonlySettings = {
  theme: "dark",
  language: "en"
};

// settings.theme = "light"; // ❌ Error: Cannot assign to 'theme'

// Useful for immutable state
function createImmutableState<T>(initial: T): Readonly<T> {
  return Object.freeze(initial);
}

Pick<T, K>#

Creates a type by picking specific properties from T.

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

type UserPreview = Pick<User, "id" | "name">;
// { id: number; name: string; }

type UserCredentials = Pick<User, "email" | "password">;
// { email: string; password: string; }

// Useful for API responses
function getUserPreview(id: number): UserPreview {
  const user = getUser(id);
  return {
    id: user.id,
    name: user.name
  };
}

Omit<T, K>#

Creates a type by omitting specific properties from T.

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

type UserWithoutPassword = Omit<User, "password">;
// { id: number; name: string; email: string; }

type PublicUser = Omit<User, "password" | "email">;
// { id: number; name: string; }

// Useful for public API responses
function getPublicProfile(id: number): PublicUser {
  const user = getUser(id);
  return {
    id: user.id,
    name: user.name
  };
}

Record<K, T>#

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

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

type Permissions = Record<Role, string[]>;
// { admin: string[]; user: string[]; guest: string[]; }

const permissions: Permissions = {
  admin: ["read", "write", "delete"],
  user: ["read", "write"],
  guest: ["read"]
};

// Creating a dictionary/map
type UserMap = Record<number, User>;

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

// Error messages
type ErrorMessages = Record<string, string>;

const errors: ErrorMessages = {
  required: "This field is required",
  email: "Please enter a valid email",
  minLength: "Must be at least 8 characters"
};

Exclude<T, U>#

Excludes types from T that are assignable to U.

type AllStatus = "pending" | "approved" | "rejected" | "cancelled";

type ActiveStatus = Exclude<AllStatus, "cancelled">;
// "pending" | "approved" | "rejected"

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

Extract<T, U>#

Extracts types from T that are assignable to U.

type AllStatus = "pending" | "approved" | "rejected" | "cancelled";

type CompletedStatus = Extract<AllStatus, "approved" | "rejected">;
// "approved" | "rejected"

type T1 = Extract<string | number | (() => void), Function>;
// () => void

NonNullable<T>#

Removes null and undefined from T.

type MaybeString = string | null | undefined;

type DefiniteString = NonNullable<MaybeString>;
// string

type User = {
  id: number;
  name: string;
  email: string | null;
};

type Email = NonNullable<User["email"]>;
// string

Function Utility Types#

ReturnType<T>#

Extracts the return type of a function.

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

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

// With async functions
async function fetchData() {
  return { data: [1, 2, 3], status: 200 };
}

type FetchResult = ReturnType<typeof fetchData>;
// Promise<{ data: number[]; status: number; }>

// Unwrap promise
type UnwrappedResult = Awaited<ReturnType<typeof fetchData>>;
// { data: number[]; status: number; }

Parameters<T>#

Extracts parameter types of a function as a tuple.

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

type CreateUserParams = Parameters<typeof createUser>;
// [name: string, age: number, email: string]

// Using with apply
function wrapper(...args: Parameters<typeof createUser>) {
  console.log("Creating user with:", args);
  return createUser(...args);
}

ConstructorParameters<T>#

Extracts constructor parameter types.

class User {
  constructor(public name: string, public age: number) {}
}

type UserConstructorParams = ConstructorParameters<typeof User>;
// [name: string, age: number]

function createInstance(...args: ConstructorParameters<typeof User>) {
  return new User(...args);
}

InstanceType<T>#

Gets the instance type of a constructor function.

class User {
  constructor(public name: string) {}
  greet() {
    return `Hello, ${this.name}`;
  }
}

type UserInstance = InstanceType<typeof User>;
// User

function processUser(user: UserInstance) {
  console.log(user.greet());
}

Advanced Utility Types#

Awaited<T>#

Recursively unwraps Promise types.

type A = Awaited<Promise<string>>;
// string

type B = Awaited<Promise<Promise<number>>>;
// number

// Useful with async functions
async function fetchUser() {
  return { id: 1, name: "Alice" };
}

type User = Awaited<ReturnType<typeof fetchUser>>;
// { id: number; name: string; }

Uppercase<T>, Lowercase<T>, Capitalize<T>, Uncapitalize<T>#

String manipulation utility types.

type Greeting = "hello world";

type Loud = Uppercase<Greeting>;
// "HELLO WORLD"

type Quiet = Lowercase<Greeting>;
// "hello world"

type Capitalized = Capitalize<Greeting>;
// "Hello world"

type Uncapitalized = Uncapitalize<"Hello World">;
// "hello World"

// Practical use with template literals
type EventName = "click" | "focus" | "blur";
type EventHandler = `on${Capitalize<EventName>}`;
// "onClick" | "onFocus" | "onBlur"

Practical Examples#

API Response Wrapper#

interface ApiResponse<T> {
  data: T;
  error: string | null;
  status: number;
}

// Utility to extract data type
type UnwrapApiResponse<T> = T extends ApiResponse<infer U> ? U : never;

type UserResponse = ApiResponse<User>;
type User = UnwrapApiResponse<UserResponse>;

// Success/Error types
type SuccessResponse<T> = Required<Pick<ApiResponse<T>, "data" | "status">> & 
  Pick<ApiResponse<T>, "error">;

type ErrorResponse = Required<Pick<ApiResponse<never>, "error" | "status">> & 
  Omit<ApiResponse<never>, "data">;

Form Field Utilities#

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

// Convert form fields to just values
type FormValues<T> = {
  [K in keyof T]: T[K] extends FormField<infer U> ? U : never;
};

interface LoginForm {
  email: FormField<string>;
  password: FormField<string>;
  rememberMe: FormField<boolean>;
}

type LoginValues = FormValues<LoginForm>;
// { email: string; password: string; rememberMe: boolean; }

// Extract errors
type FormErrors<T> = Partial<Record<keyof T, string>>;

const errors: FormErrors<LoginForm> = {
  email: "Invalid email",
  password: "Password too short"
};

State Management#

interface State {
  user: User | null;
  posts: Post[];
  loading: boolean;
  error: string | null;
}

// Make entire state readonly
type ReadonlyState = Readonly<State>;

// Make specific parts updatable
type UpdatableState = Pick<State, "user" | "posts">;

// Create action types
type StateKeys = keyof State;
type SetAction<K extends StateKeys> = {
  type: `SET_${Uppercase<K>}`;
  payload: State[K];
};

type SetUserAction = SetAction<"user">;
// { type: "SET_USER"; payload: User | null; }

Typed Event Handlers#

interface DOMEvents {
  click: MouseEvent;
  keydown: KeyboardEvent;
  submit: SubmitEvent;
  change: Event;
}

type EventHandler<K extends keyof DOMEvents> = (event: DOMEvents[K]) => void;

const handleClick: EventHandler<"click"> = (event) => {
  console.log(event.clientX, event.clientY); // MouseEvent properties
};

const handleKeydown: EventHandler<"keydown"> = (event) => {
  console.log(event.key); // KeyboardEvent properties
};

// Generic event listener
function addEventListener<K extends keyof DOMEvents>(
  element: HTMLElement,
  eventType: K,
  handler: EventHandler<K>
): void {
  element.addEventListener(eventType, handler as EventListener);
}

Database Query Builder#

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

// Select specific fields
type SelectFields<T, K extends keyof T> = Pick<T, K>;

type UserPreview = SelectFields<User, "id" | "name">;
// { id: number; name: string; }

// Update payload (all optional except id)
type UpdatePayload<T> = Partial<Omit<T, "id">> & Pick<T, "id">;

type UserUpdate = UpdatePayload<User>;
// { id: number; name?: string; email?: string; age?: number; createdAt?: Date; }

// Create payload (omit auto-generated fields)
type CreatePayload<T> = Omit<T, "id" | "createdAt">;

type UserCreate = CreatePayload<User>;
// { name: string; email: string; age: number; }

Deep Partial#

// Make all properties (including nested) optional
type DeepPartial<T> = {
  [P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P];
};

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

type PartialConfig = DeepPartial<Config>;

const config: PartialConfig = {
  database: {
    host: "localhost"
    // port, credentials are optional
  }
  // cache is optional
};

Type-safe Object Keys#

// Better than Object.keys
function getKeys<T extends object>(obj: T): Array<keyof T> {
  return Object.keys(obj) as Array<keyof T>;
}

const user = { id: 1, name: "Alice", email: "alice@example.com" };
const keys = getKeys(user); // ("id" | "name" | "email")[]

keys.forEach(key => {
  console.log(user[key]); // Type-safe access
});

Combining Utility Types#

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

// Public profile: pick specific fields and make readonly
type PublicProfile = Readonly<Pick<User, "id" | "name">>;

// Update DTO: omit auto-generated, make rest optional
type UpdateUserDto = Partial<Omit<User, "id" | "createdAt" | "updatedAt">>;

// Registration form: pick + make password required
type RegistrationForm = Pick<User, "name" | "email" | "password">;

// Safe user (without password)
type SafeUser = Omit<User, "password">;

// Admin user: safe user + admin role
type AdminUser = Omit<User, "password"> & { role: "admin" };

Best Practices#

  1. Use utility types to reduce duplication

    type UpdateDto = Partial<Omit<User, "id">>;
    // Instead of manually redefining all fields
    

  2. Combine utility types for complex transformations

    type PublicUser = Readonly<Pick<User, "id" | "name">>;
    

  3. Prefer utility types over manual type definitions

    type UserKeys = keyof User;
    // Instead of: type UserKeys = "id" | "name" | "email";
    

  4. Use ReturnType for deriving types from functions

    type User = ReturnType<typeof getUser>;
    

  5. Don't overuse - keep types readable

    // ❌ Too complex
    type X = Partial<Pick<Omit<Required<User>, "id">, "name" | "email">>;
    
    // ✅ Better - break it down
    type UserWithoutId = Omit<User, "id">;
    type UserBasics = Pick<UserWithoutId, "name" | "email">;
    

Next Steps#