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.

Enjoyed this essay? Share it.

Related essays

Next essay
TypeScript Advanced Patterns: Master Type-Level Programming