In the previous tutorial, we learned about modules and namespaces. Now let’s learn about utility types — built-in types that transform other types.

TypeScript includes many utility types out of the box. They save you from writing complex type logic yourself. By the end of this tutorial, you will know when and how to use each one.

Why Utility Types?

Imagine you have a User type:

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

Now you need a function that updates a user. But you want to allow partial updates — only some fields, not all. Without utility types, you would need to create a new type manually:

// Tedious — you repeat every field with ?
interface UserUpdate {
  id?: number;
  name?: string;
  email?: string;
  age?: number;
}

With utility types, one line does the job:

type UserUpdate = Partial<User>;

Partial

Partial<T> makes all properties optional:

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

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

Real-World Use: Update Functions

function updateUser(id: number, updates: Partial<User>): User {
  const existing = getUserById(id);
  return { ...existing, ...updates };
}

// Only update the name — other fields stay the same
updateUser(1, { name: "Sam" });

// Update multiple fields
updateUser(1, { name: "Sam", email: "sam@example.com" });

Required

Required<T> is the opposite of Partial. It makes all properties required:

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

type FullConfig = Required<Config>;
// Result:
// {
//   host: string;
//   port: number;
//   debug: boolean;
// }

This is useful when you have a config with defaults and want to ensure all values are set after merging:

const defaults: Required<Config> = {
  host: "localhost",
  port: 3000,
  debug: false,
};

function createConfig(overrides: Config): Required<Config> {
  return { ...defaults, ...overrides };
}

Readonly

Readonly<T> makes all properties readonly:

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

type ReadonlyUser = Readonly<User>;
// Result:
// {
//   readonly id: number;
//   readonly name: string;
// }

const user: ReadonlyUser = { id: 1, name: "Alex" };
// user.name = "Sam"; // Error: Cannot assign to 'name' because it is a read-only property

Use this for data that should never change after creation, like database records or API responses.

Pick<T, K>

Pick<T, K> creates a new type with only the specified properties:

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

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

Real-World Use: API Responses

// Full user for internal use
interface User {
  id: number;
  name: string;
  email: string;
  passwordHash: string;
  createdAt: Date;
}

// Public user for API responses — no sensitive fields
type PublicUser = Pick<User, "id" | "name" | "email">;

function toPublicUser(user: User): PublicUser {
  return {
    id: user.id,
    name: user.name,
    email: user.email,
  };
}

Omit<T, K>

Omit<T, K> is the opposite of Pick. It creates a type with all properties except the specified ones:

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

type SafeUser = Omit<User, "passwordHash">;
// Result:
// {
//   id: number;
//   name: string;
//   email: string;
// }

Real-World Use: Create vs Read Types

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

// When creating a user, id and createdAt are generated by the server
type CreateUser = Omit<User, "id" | "createdAt">;

function createUser(data: CreateUser): User {
  return {
    ...data,
    id: generateId(),
    createdAt: new Date(),
  };
}

createUser({ name: "Alex", email: "alex@example.com" });

Record<K, T>

Record<K, T> creates an object type where all keys have the same value type:

type Role = "admin" | "editor" | "viewer";

type RolePermissions = Record<Role, string[]>;

const permissions: RolePermissions = {
  admin: ["read", "write", "delete"],
  editor: ["read", "write"],
  viewer: ["read"],
};

If you forget a key, TypeScript shows an error:

// Error: Property 'viewer' is missing
const bad: RolePermissions = {
  admin: ["read"],
  editor: ["read"],
};

Real-World Use: Lookup Maps

type StatusCode = 200 | 404 | 500;

const statusMessages: Record<StatusCode, string> = {
  200: "OK",
  404: "Not Found",
  500: "Internal Server Error",
};

Exclude<T, U> and Extract<T, U>

These work on union types, not object types.

Exclude<T, U>

Removes types from a union:

type Status = "active" | "inactive" | "banned" | "deleted";

type ActiveStatus = Exclude<Status, "banned" | "deleted">;
// Result: "active" | "inactive"

Extract<T, U>

Keeps only types that match:

type Status = "active" | "inactive" | "banned" | "deleted";

type DangerousStatus = Extract<Status, "banned" | "deleted">;
// Result: "banned" | "deleted"

Real-World Use: Filtering Event Types

type Event =
  | { type: "click"; x: number; y: number }
  | { type: "keypress"; key: string }
  | { type: "scroll"; offset: number };

// Extract only mouse events
type MouseEvent = Extract<Event, { type: "click" }>;
// Result: { type: "click"; x: number; y: number }

NonNullable

Removes null and undefined from a type:

type MaybeString = string | null | undefined;

type DefiniteString = NonNullable<MaybeString>;
// Result: string

Useful for cleaning up types from optional values:

interface User {
  name: string;
  nickname?: string;
}

type Nickname = NonNullable<User["nickname"]>;
// Result: string (removed undefined)

ReturnType and Parameters

ReturnType

Gets the return type of a function:

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

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

This is useful when you don’t want to define a separate type for the return value.

Parameters

Gets the parameter types as a tuple:

function createUser(name: string, age: number, email: string): void {
  // ...
}

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

// You can extract individual parameters
type FirstParam = CreateUserParams[0]; // string

Awaited

Unwraps Promise types:

type PromiseString = Promise<string>;
type Result = Awaited<PromiseString>;
// Result: string

// Works with nested promises too
type Nested = Promise<Promise<number>>;
type FinalResult = Awaited<Nested>;
// Result: number

Real-World Use: Extracting API Response Types

async function fetchUsers(): Promise<User[]> {
  const res = await fetch("/api/users");
  return res.json();
}

type Users = Awaited<ReturnType<typeof fetchUsers>>;
// Result: User[]

Combining Utility Types

The real power comes from combining multiple utility types:

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

// Partial update without sensitive fields
type UserUpdate = Partial<Omit<User, "id" | "password" | "createdAt">>;
// Result:
// {
//   name?: string;
//   email?: string;
// }

// Readonly public user
type PublicUser = Readonly<Pick<User, "id" | "name" | "email">>;
// Result:
// {
//   readonly id: number;
//   readonly name: string;
//   readonly email: string;
// }

Quick Reference Table

Utility TypeWhat It DoesExample
Partial<T>All properties optionalPartial<User>
Required<T>All properties requiredRequired<Config>
Readonly<T>All properties readonlyReadonly<User>
Pick<T, K>Keep specific propertiesPick<User, "id" | "name">
Omit<T, K>Remove specific propertiesOmit<User, "password">
Record<K, T>Object with typed keys and valuesRecord<string, number>
Exclude<T, U>Remove types from unionExclude<Status, "banned">
Extract<T, U>Keep matching types from unionExtract<Event, { type: "click" }>
NonNullable<T>Remove null and undefinedNonNullable<string | null>
ReturnType<T>Get function return typeReturnType<typeof fn>
Parameters<T>Get function parameter typesParameters<typeof fn>
Awaited<T>Unwrap Promise typeAwaited<Promise<string>>

What’s Next?

In the next tutorial, we will learn about mapped types and conditional types — how to build your own utility types and transform types programmatically.