Skip to content
All essays
WebFebruary 8, 202513 min

TypeScript Utility Types: Complete Reference Guide

Complete guide to TypeScript utility types. Learn Partial, Required, Readonly, Pick, Omit, and more with examples.

Ü
Ümit Uz
Mobile & Full Stack Developer

Built-in Utility Types

Partial<T>

Makes all properties optional.

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

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

function updateUser(id: number, updates: Partial<User>) {
  // Merge updates with existing user
  return { ...existingUser, ...updates };
}

updateUser(1, { age: 25 });  // Only update age

Required<T>

Makes all properties required.

typescript
interface PartialUser {
  id?: number;
  name?: string;
  email?: string;
}

type CompleteUser = Required<PartialUser>;
// {
//   id: number;
//   name: string;
//   email: string;
// }

Readonly<T>

Makes all properties readonly.

typescript
interface Config {
  apiUrl: string;
  timeout: number;
}

type ReadonlyConfig = Readonly<Config>;

const config: ReadonlyConfig = {
  apiUrl: 'https://api.example.com',
  timeout: 5000,
};

// config.apiUrl = 'new url';  // Error: Cannot assign

Record<K, T>

Creates an object type with specified keys and values.

typescript
type UserMap = Record<string, User>;

const users: UserMap = {
  user1: { id: 1, name: 'John' },
  user2: { id: 2, name: 'Jane' },
};

// More specific keys
type Roles = Record<'admin' | 'user' | 'guest', number>;

const permissions: Roles = {
  admin: 7,
  user: 4,
  guest: 1,
};

Pick<T, K>

Selects specific properties from a type.

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

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

function getUserInfo(user: User): UserBasicInfo {
  const { id, name } = user;
  return { id, name };
}

Omit<T, K>

Removes specific properties from a type.

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

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

function toPublicUser(user: User): PublicUser {
  const { password, ...publicUser } = user;
  return publicUser;
}

Exclude<T, U>

Removes types from a union.

typescript
type Primitives = string | number | boolean | null | undefined;

type NonNullable = Exclude<Primitives, null | undefined>;
// string | number | boolean

type OnlyStrings = Exclude<string | number | boolean, number | boolean>;
// string

Extract<T, U>

Extracts types from a union.

typescript
type All = string | number | boolean | Function;

type FunctionsOnly = Extract<All, Function>;
// Function

type StringsAndNumbers = Extract<All, string | number>;
// string | number

NonNullable<T>

Removes null and undefined from a type.

typescript
type Value = string | null | undefined;

type NonNullValue = NonNullable<Value>;
// string

function process(value: NonNullValue) {
  // value is guaranteed not to be null or undefined
  return value.toUpperCase();
}

ReturnType<T>

Extracts the return type of a function type.

typescript
function getUser(): {
  id: number;
  name: string;
} {
  return { id: 1, name: 'John' };
}

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

async function fetchData(): Promise<number> {
  return 42;
}

type DataReturn = ReturnType<typeof fetchData>;
// Promise<number>

InstanceType<T>

Extracts the instance type of a class.

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

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

function createUser(Class: new (...args: any) => any) {
  return new Class();
}

const user = createUser(User);  // User

Custom Utility Types

DeepPartial

typescript
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;
    };
  };
}

type PartialConfig = DeepPartial<Config>;
// Can partially specify nested properties

DeepRequired

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

DeepReadonly

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

interface State {
  user: {
    name: string;
    preferences: {
      theme: string;
    };
  };
}

type ReadonlyState = DeepReadonly<State>;
// All nested properties are readonly

Mutable

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

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

type MutableInterface = Mutable<ReadonlyInterface>;
// {
//   id: number;
//   name: string;
// }

Tuple Operations

typescript
type Push<T extends any[], V> = [...T, V];
type Unshift<T extends any[], V> = [V, ...T];

type Tuple = [1, 2];
type WithPush = Push<Tuple, 3>;  // [1, 2, 3]
type WithUnshift = Unshift<Tuple, 0>;  // [0, 1, 2]

type First<T extends any[]> = T extends [infer F, ...any[]] ? F : never;
type Last<T extends any[]> = T extends [...any[], infer L] ? L : never;

type Head = First<[1, 2, 3]>;  // 1
type Tail = Last<[1, 2, 3]>;  // 3

Function Types

typescript
type Arguments<T> = T extends (...args: infer A) => any ? A : never;

function example(name: string, age: number): boolean {
  return true;
}

type ExampleArgs = Arguments<typeof example>;
// [string, number]

Practical Examples

API Response Types

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

// Create user - only provide required fields
type CreateUserInput = Omit<User, 'id'>;

// Update user - all fields optional
type UpdateUserInput = Partial<CreateUserInput>;

// Public user - hide sensitive fields
type PublicUser = Omit<User, 'email' | 'password'>;

async function createUser(input: CreateUserInput): Promise<PublicUser> {
  const user = await db.create(input);
  const { email, ...publicUser } = user;
  return publicUser;
}

Form Handling

typescript
interface FormData {
  username: string;
  email: string;
  password: string;
  confirmPassword: string;
}

// Form state - all fields optional
type FormState = Partial<FormData>;

// Form errors - all fields optional string
type FormErrors = {
  [K in keyof FormData]?: string;
};

const errors: FormErrors = {
  username: 'Username is required',
  email: 'Invalid email',
};

Configuration Types

typescript
interface DatabaseConfig {
  host: string;
  port: number;
  username: string;
  password: string;
}

interface ServerConfig {
  port: number;
  database: DatabaseConfig;
}

// Partial config for development
type DevConfig = Partial<ServerConfig>;

// Required config for production
type ProdConfig = Required<ServerConfig>;

Best Practices

  1. 1Compose utility types: Combine multiple utility types
  2. 2Create custom utilities: For reusable patterns
  3. 3Use type inference: Let TypeScript infer when possible
  4. 4Prefer utility types: Over manual type definitions
  5. 5Document complex types: Help other developers understand

Conclusion

Utility types provide powerful tools for type manipulation. Use them to write cleaner, more maintainable TypeScript code.

Related essays

Next essay
TypeScript Advanced Patterns: Master Type-Level Programming