jsguides

TypeScript Utility Types: A Complete Tutorial

Introduction

TypeScript ships with a standard library of type-level helpers that transform existing types without requiring you to write new interfaces or type aliases from scratch. These built-in utility types—Partial, Required, Pick, Omit, Record, Extract, Exclude, ReturnType, and Parameters—handle the most common type transformations you encounter in application code. Learning them means less boilerplate and more expressive type definitions.

TypeScript utility types are built-in type transformations that let you derive new types from existing ones without writing redundant interfaces. These utility types—Partial, Required, Pick, Omit, Record, and others—are part of the TypeScript compiler and can dramatically reduce boilerplate in your type definitions. In this tutorial, you’ll learn the most commonly used utility types and how to apply them in real-world scenarios.

Partial and Required

The Partial<T> utility type constructs a type with all properties of T set to optional. This is useful when you want to update an object partially.

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

// All properties are now optional
type PartialUser = Partial<User>;

// This is valid - we can provide only some properties
function updateUser(id: number, updates: PartialUser): void {
  console.log(`Updating user ${id}`, updates);
}

updateUser(1, { name: "Alice" }); // Only updating name
updateUser(1, { email: "alice@example.com", age: 30 }); // Multiple updates

Partial<T> is ideal for function arguments where callers provide only the fields they want to change. But what about the reverse case? Sometimes you receive an incomplete configuration object from user input or a config file and need to assert that every field has been filled in before using it. Required<T> turns every optional property into a required one, forcing you to supply all values:

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

const fullConfig: Required<Config> = {
  host: "localhost",
  port: 8080,
  ssl: true,
};

Required<T> works well when you need a complete object shape but started from an interface with optional fields. Once you’ve used Partial and Required, you often need a different transformation: narrowing a large type down to only the fields a specific view needs. That is where Pick<T, K> comes in:

Pick and Omit

Pick<T, K> creates a new type by selecting specific properties K from type T.

interface Article {
  id: number;
  title: string;
  content: string;
  author: string;
  publishedAt: Date;
  status: "draft" | "published" | "archived";
}

// Only pick the fields we need for a preview
type ArticlePreview = Pick<Article, "id" | "title" | "author">;

const preview: ArticlePreview = {
  id: 1,
  title: "Understanding TypeScript Utility Types",
  author: "John Doe",
};

Pick selects the fields you want, which is useful for building previews, summaries, or DTOs from a larger domain type. Sometimes it is easier to think in terms of what to remove rather than what to keep. Omit<T, K> takes a type and a set of keys and returns the type without those keys:

Omit<T, K> does the opposite: it creates a type by excluding specific properties K from T.

type ArticleWithoutContent = Omit<Article, "content">;

const articleSummary: ArticleWithoutContent = {
  id: 1,
  title: "Understanding TypeScript Utility Types",
  author: "John Doe",
  publishedAt: new Date(),
  status: "published",
};

How utility types fit together

The built-in utility types are most useful when you compose them instead of treating each one as a standalone trick. Partial, Pick, and Omit often appear together because they describe three different views of the same source type. One view relaxes required fields, one selects a subset, and one removes a subset. Thinking in terms of type transformations helps you decide which utility answers the real question your code is asking.

Partial and Required in practice

Partial<T> is a good fit for update forms, patch objects, and configuration overrides because callers rarely need to provide every field at once. Required<T> is the mirror image: it is useful when you want to turn an optional shape into something that must be fully present before use. Together they make it easy to model a two-step flow, such as collecting incomplete input and then validating or finalizing it into a complete object.

Selective shapes with pick and omit

Pick and Omit are the best tools when most of a type is correct, but a different slice of that type is needed for a specific view. That is common in UI code, where a list view might need only a summary and a detail page needs the full record. Using Pick and Omit keeps the relationship to the source type visible, which is safer than redefining a new shape from scratch and accidentally drifting away from the original contract.

Record

The Record<K, T> utility type constructs an object type whose property keys are K and values are T. It’s perfect for creating dictionaries or mapped types.

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

interface Permission {
  canRead: boolean;
  canWrite: boolean;
  canDelete: boolean;
}

const rolePermissions: Record<Role, Permission> = {
  admin: { canRead: true, canWrite: true, canDelete: true },
  user: { canRead: true, canWrite: true, canDelete: false },
  guest: { canRead: true, canWrite: false, canDelete: false },
};

function checkPermission(role: Role, action: keyof Permission): boolean {
  return rolePermissions[role][action];
}

console.log(checkPermission("admin", "canDelete")); // true
console.log(checkPermission("guest", "canWrite"));  // false

Record locks down the key-value mapping you care about, but sometimes you need the opposite: filtering a union type to keep or remove certain branches. TypeScript provides Extract and Exclude for exactly that. They operate on union members individually, making them especially useful for tagged union patterns:

Extract and Exclude

Extract<T, U> extracts types from T that are assignable to U.

type T = Extract<"a" | "b" | "c" | "d", "a" | "c" | "f">;
// Result: "a" | "c"

type Numbers = Extract<string | number | boolean, number>;
// Result: number

// Practical example: extracting certain event types
type Event =
  | { type: "click"; x: number; y: number }
  | { type: "keydown"; key: string }
  | { type: "focus" }
  | { type: "blur" };

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

Extract narrows a union by keeping only members that match a second type. When you need the inverse—removing certain members while keeping everything else—Exclude does that job. It is often used to strip null and undefined from a type, or to define a type as “everything except these specific cases”:

Exclude<T, U> does the opposite: it removes types from T that are assignable to U.

type T = Exclude<"a" | "b" | "c" | "d", "a" | "c">;
// Result: "b" | "d"

type NotNull = Exclude<string | number | null | undefined, null | undefined>;
// Result: string | number

Utility types and unions

Some utility types work by distributing across unions, which is why they are so flexible. If a type is a union of several possibilities, Extract and Exclude can keep or remove the members you care about one by one. That makes them ideal for narrowing event types, API states, and other tagged unions. The behavior is predictable once you remember that the helper is applied to each member of the union, then the compiler recombines the results.

ReturnType and ParameterType

ReturnType<T> extracts the return type of a function type T.

function getUser() {
  return { id: 1, name: "Alice" };
}

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

function getUsers() {
  return [{ id: 1, name: "Alice" }, { id: 2, name: "Bob" }];
}

type UserList = ReturnType<typeof getUsers>;
// Result: { id: number; name: string }[]

ReturnType captures what a function produces, which is useful when you want to type a value that originates from a function call without manually declaring the shape. TypeScript also provides the reverse: Parameters<T> extracts the tuple of parameter types a function expects. This is handy when you need to store or forward arguments before calling the function:

ParameterType<T> (or Parameters<T>) extracts the parameter types of a function.

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

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

const params: CreateUserParams = ["Alice", 30, "alice@example.com"];
const user = createUser(...params);

Choosing the right utility

When several utilities could work, choose the one that says the most about your intent. If you want a subset of fields, use Pick. If you want to remove fields, use Omit. If you want to relax or tighten optionality, use Partial or Required. If you are working with function signatures, use Parameters or ReturnType. The best utility is usually the one a teammate can read and immediately map back to the shape they already know.

Practical example: API response handler

Putting utility types together in a single file reveals how they complement each other. Consider an API response that arrives as a full object but gets consumed in three different ways: a partial update form, a list summary, and a validation guard. Each view needs a different slice of the same base type:

Here’s how you can combine utility types in a real-world scenario:

interface ApiResponse {
  id: number;
  title: string;
  body: string;
  userId: number;
  createdAt: string;
  updatedAt: string;
}

// We want a form that can partially update an article
type UpdateArticleForm = Partial<Pick<ApiResponse, "title" | "body">>;

// We want to display article summaries in a list
type ArticleSummary = Pick<ApiResponse, "id" | "title" | "createdAt">;

// We want to validate incoming API data
function validateArticle(data: unknown): data is Partial<ApiResponse> {
  if (typeof data !== "object" || data === null) return false;
  const obj = data as Record<string, unknown>;
  return (
    obj.title === undefined || typeof obj.title === "string"
  ) && (
    obj.body === undefined || typeof obj.body === "string"
  );
}

Why the built-ins are enough

The standard library already covers the most common transformations, and that is a good thing. Built-in names carry a lot of meaning for TypeScript users, so a familiar utility often communicates the idea faster than a custom helper. That does not mean you should never write your own types, but it does mean you should start from the built-ins first. Doing so keeps your code smaller, reduces cognitive load, and makes your types easier to compare with examples in the wider TypeScript ecosystem.

Practical design habits

When you are deciding between utility types, start from the shape you already have and ask what should change. Do you want fewer fields, more required fields, or a different view of a function signature? That question usually points straight to the right helper. A small habit like naming each transformation clearly can make a big difference later, especially when several utilities are layered together in one file.

Keep the examples close to the real code

Utility types are easier to remember when the examples resemble actual application code. Update forms, preview cards, permission maps, and API payloads are all good places to practice. Those examples make it easier to see why a helper exists and what problem it solves. They also make it easier to explain the type later to someone who has not memorized the standard library yet.

That practical mindset is the real payoff of utility types: they let you describe the shape you need without rewriting the whole shape from scratch.

Use transformation names with care

Utility types work best when the surrounding names make their purpose obvious. A type like UpdateArticleForm or ArticleSummary reads as a concrete idea, while a generic helper name leaves the reader guessing what changed. Naming the transformation helps the type feel like part of the domain instead of a compiler trick. That is especially useful in larger codebases where the same base type gets reshaped in a few different ways.

It is also worth keeping the utility chain short. Once a type starts stacking many helpers on top of one another, the meaning can get lost in the composition. When that happens, a small intermediate alias often makes the design clearer. A named step gives the reader a place to stand before moving to the next transformation.

Summary

TypeScript’s utility types are powerful tools that let you transform and manipulate types with ease:

  • Partial/Required: Toggle property optionality
  • Pick/Omit: Select or exclude specific properties
  • Record: Create object types with specific keys and value types
  • Extract/Exclude: Filter union types
  • ReturnType/Parameters: Extract function type information

These utilities reduce boilerplate and make your type definitions more expressive and maintainable. In the next tutorial, we’ll explore decorators in TypeScript.

Next steps

  • Rewrite one of your existing interfaces using Pick and Omit instead of a manually maintained subset type. Compare the two approaches after a week: which one stayed in sync with the source type?
  • Try replacing a pattern where you define a separate “update” type with Partial<Pick<Original, 'field1' | 'field2'>>. This composition often replaces a manually maintained second interface.
  • Use Parameters<typeof someFn> to tighten up argument forwarding in a utility function that wraps an existing callback.

See Also