Skip to content

TypeScript Utility Types: Practical Usage Patterns

Master TypeScript's built-in utility types: Partial, Required, Pick, Omit, Record, Exclude, and Extract with real-world examples for cleaner, safer code.

· · 14 min read

Updated: October 2, 2026

TypeScript utility type code with Partial and Pick transformations shown in a VS Code editor

Quick Take

TypeScript ships 20+ built-in utility types that eliminate most type duplication: Partial for optional forms, Pick and Omit for API shapes, Record for dictionaries, Exclude and Extract for union filtering. Knowing which utility type to reach for cuts type annotation boilerplate in half.

Run this yourself. Every utility type below, compiled under strict TypeScript 7 and then executed with runtime assertions: typescript-utility-types/ in the Coding Dunia code-examples repo.

TypeScript's built-in utility types cover 90% of real-world type transformation needs. Partial<T> makes all properties optional (used for PATCH payloads and form drafts). Required<T> makes all properties mandatory (used after validation). Pick<T, K> creates a type with only the listed keys; Omit<T, K> creates a type with those keys removed, use Pick for small subsets, Omit when removing one or two fields from a large interface. Record<K, V> creates a typed object where all keys share one value type, and TypeScript flags missing keys at compile time when the key is a union. Exclude<T, U> removes members from a union; Extract<T, U> keeps only matching members. All seven are globally available since TypeScript 2.x with no import statement. They compose freely: Partial<Pick<User, 'name' | 'email'>> is a valid, readable pattern.

Utility types are one of those TypeScript features that you don't appreciate until the day you stop duplicating interfaces everywhere. Plenty of codebases give every form, every API payload, and every partial update its own hand-written type. It's tedious. Worse, those types drift apart over time.

What Are TypeScript Utility Types?

TypeScript's built-in utility types are generic types that transform other types. They're available globally in TypeScript 2.8+ (Exclude, Extract) and TypeScript 2.1+ (Partial, Required, Pick, Omit). No import statement needed. They do the kind of type manipulation that you'd otherwise write with mapped types and generics by hand.

The short list you'll use every week: Partial<T>, Required<T>, Pick<T, K>, Omit<T, K>, Record<K, T>, Exclude<T, U>, and Extract<T, U>. Utility types are TypeScript's built-in generics that derive a new type from an existing one instead of forcing you to hand-write a near-duplicate interface. According to the TypeScript Handbook's utility types reference, the full set now numbers around twenty distinct helpers. My view is that seven of them cover most of what application code needs, and the rest are situational: you meet them when a conditional type or a ReturnType<T> extraction comes up. The seven core ones, Partial, Required, Pick, Omit, Record, Exclude, and Extract, are all globally available without an import statement. Partial, Pick and Record arrived in TypeScript 2.1, Required, Exclude and Extract in 2.8, and Omit in 3.5, so there is no version gate stopping you from using them today, whether the project is on TypeScript 4.9 or the newer 5.x releases.

Most of the interfaces you hand-write are just Pick, Omit, or Partial of a type you already have.

Share this Post on X Bluesky

How Does Partial Work?

Partial<T> makes every property of T optional. Partial is the utility type that takes an existing interface and returns a version where every field carries an implicit ?, without you having to retype the shape by hand. The most common use case is update payloads, where you only send the fields that changed. If the T you need doesn't exist yet because the shape came off an API you don't control, the JSON to TypeScript converter turns a sample response into the base interface, and everything below composes on top of it. I'd type a PATCH payload with Partial<T> by default, and write a separate update interface only when the update follows different rules than the base, such as a field that can be cleared with null. The alternative, writing a second interface with a ? bolted onto every field, works fine on day one but drifts the moment someone adds a field to the base type and forgets the update variant. Partial<T> cannot drift because it is derived, not duplicated, so the update type always tracks the source interface automatically.

interface User {
  id: string;
  name: string;
  email: string;
  role: 'admin' | 'editor' | 'viewer';
}

// All fields are now optional
type UserUpdate = Partial<User>;

async function updateUser(id: string, changes: UserUpdate) {
  return fetch(`/api/users/${id}`, {
    method: 'PATCH',
    body: JSON.stringify(changes),
  });
}

// TypeScript accepts any subset of fields
await updateUser('u-1', { name: 'Alice' });
await updateUser('u-1', { role: 'admin', email: 'alice@example.com' });

In practice, Partial<T> is most valuable in PATCH endpoints and form-state types. Without it, you'd write a second interface with ? on every field.

How Does Required Work?

Required<T> is the inverse. It removes all ? marks, making every optional property mandatory. This matters most at the boundary between untrusted input and validated internal state, the two or three lines right after a config file loads or a form submits. According to the TypeScript Handbook, Required<T> is implemented internally as a mapped type that strips the optional modifier from every key, which is the same mechanism Partial<T> uses in reverse to add it. I'd expect to use it about once per project, at the seam between "raw, possibly-incomplete config" and "resolved config the rest of the app can trust." Skipping this step is how you end up with timeout typed as number | undefined forty call sites deep, forcing every one of those forty call sites to handle a case that, by that point in the program, can never actually happen.

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

// After loading + validation, every field must be present
type ResolvedConfig = Required<Config>;

function validateConfig(raw: Config): ResolvedConfig {
  if (!raw.apiUrl) throw new Error('apiUrl is required');
  return {
    apiUrl: raw.apiUrl,
    timeout: raw.timeout ?? 5000,
    retries: raw.retries ?? 3,
  };
}

The return type of validateConfig guarantees callers that all fields are safe to access without null checks.

Chisels and hand tools neatly arranged and strapped to a workshop wall
Photo by Ahmet Kurt on Unsplash

How Do Pick and Omit Shape Interfaces?

Pick<T, K> selects a subset of keys. Omit<T, K> removes specific keys. Both create new types derived from an existing one. Pick and Omit are the two utility types you reach for whenever a component or endpoint needs a narrower view of a wider model rather than the whole thing. I'd build API response types from Omit<InternalRecord, 'passwordHash' | 'internalNotes'> patterns: three or four sensitive fields stripped, everything else passed through untouched, and that single line does the job that used to take a fifteen-field interface written by hand. The tradeoff between the two is mostly about how many fields you are naming: per the TypeScript documentation, both compile to the same kind of mapped type under the hood, so there is no runtime cost difference, the choice is purely about readability at the call site.

interface Product {
  id: string;
  name: string;
  price: number;
  stock: number;
  createdAt: Date;
  updatedAt: Date;
}

// Only expose what the UI needs
type ProductCard = Pick<Product, 'id' | 'name' | 'price'>;

// Strip internal timestamps from the public API response
type PublicProduct = Omit<Product, 'createdAt' | 'updatedAt'>;

Which should you use? Pick is better when the subset is small. Omit is better when you're removing one or two fields from a large interface. If you find yourself listing 8 fields in a Pick, switch to Omit.

Combining Pick and Partial for Form State

This pattern shows up in almost every real form codebase:

// Full user type
interface User {
  id: string;
  name: string;
  email: string;
  bio: string;
}

// Editable fields only, all optional for draft state
type ProfileDraft = Partial<Pick<User, 'name' | 'email' | 'bio'>>;

The type says exactly what it means: a draft profile edit where any combination of name, email, or bio can be present.

How Does Record Work?

Record<K, V> creates an object type where all keys are of type K and all values are of type V. It's the typed alternative to { [key: string]: SomeType }. Record is the utility type that pairs a key union with a value type and forces every member of that union to have a corresponding entry, which is exactly what an index signature cannot do on its own. The difference matters more than it looks: an index signature accepts any string key and silently returns undefined for one that is missing, while Record<HttpStatus, string> fails to compile the moment one of the seven status codes lacks a message. I'd reach for it first to catch missing i18n translation keys before merge: one union of message IDs, one Record per locale file, and TypeScript refuses to build if any locale lacks an entry. Drop a key from a Record<MessageId, string> and tsc 5.9.3 answers with error TS2741, Property 'error' is missing in type. That is a compile-time guarantee an index signature simply cannot offer.

type HttpStatus = 200 | 201 | 400 | 401 | 403 | 404 | 500;

const statusMessages: Record<HttpStatus, string> = {
  200: 'OK',
  201: 'Created',
  400: 'Bad Request',
  401: 'Unauthorized',
  403: 'Forbidden',
  404: 'Not Found',
  500: 'Internal Server Error',
};

// TypeScript ensures every HttpStatus key is present
// Adding a new status to HttpStatus forces you to add a message too

Using a union as the key type is where Record really shines. TypeScript will flag missing keys at compile time.

A pegboard wall packed with tools above a cluttered workbench
Photo by camera obscura on Unsplash

What Do Exclude and Extract Do?

These work on union types, not object types. That distinction trips people up, mostly because Pick and Omit operate on object keys while Exclude and Extract operate on the members of a union, and the syntax looks similar enough that it's easy to reach for the wrong one. Exclude<T, U> is the utility type that removes any member of union T that also appears in U, leaving the rest untouched. TypeScript 2.8 shipped both Exclude and Extract in the same release that introduced conditional types, since both are implemented as conditional types under the hood, per the TypeScript release notes for that version. Two common uses cover most real code: filtering null and undefined out of a nullable union, and pulling a single variant out of a discriminated union without writing a type guard by hand.

type Status = 'active' | 'inactive' | 'pending' | 'banned';

// Remove specific members from the union
type ActiveStatus = Exclude<Status, 'banned' | 'inactive'>;
// Result: 'active' | 'pending'

// Keep only the members that match
type ClosedStatus = Extract<Status, 'inactive' | 'banned'>;
// Result: 'inactive' | 'banned'

A practical use: filtering out null and undefined from a union.

type MaybeString = string | null | undefined;

type DefiniteString = Exclude<MaybeString, null | undefined>;
// Result: string

TypeScript 2.8 actually uses Exclude to implement NonNullable<T> internally. So NonNullable<T> is just Exclude<T, null | undefined> under the hood.

How Do ReturnType, Parameters and Awaited Derive Types From Functions?

Everything above reshapes object types. A second family reads types off a function, which is how you avoid writing the same shape twice when a function is the source of truth. Parameters<F> gives you the argument list as a tuple, ReturnType<F> gives you what it returns, and Awaited<T> unwraps a Promise.

export async function fetchUser(id: string, includeRole = false): Promise<User> {
  // ...
}

export type FetchUserArgs = Parameters<typeof fetchUser>;
// [id: string, includeRole?: boolean | undefined]

export type FetchUserPromise = ReturnType<typeof fetchUser>;
// Promise<User>

export type FetchUserResult = Awaited<ReturnType<typeof fetchUser>>;
// User

Change the signature of fetchUser and all three follow. That's the entire point, and it beats a hand-written interface FetchUserResult that drifts the first time someone adds a field.

The trap is the middle line. ReturnType of an async function is the Promise, not the value inside it, so pending.name on a FetchUserPromise is a compile error: Property 'name' does not exist on type 'Promise<User>' (TS2339). Does that look obvious written down? It stops being obvious at 5 p.m. in a file where the type is three aliases away. Reach for Awaited<ReturnType<...>> any time the function is async.

When you control the function you're wrapping, inference is cleaner than Parameters plus ReturnType. Let the compiler capture the argument tuple and the return type as two type parameters:

export function withCallLog<A extends unknown[], R>(
  fn: (...args: A) => R,
  log: string[],
): (...args: A) => R {
  return (...args) => {
    log.push(args.map(String).join(','));
    return fn(...args);
  };
}

const loggedFetchUser = withCallLog(fetchUser, []);

loggedFetchUser keeps the exact signature of fetchUser, optional parameter included. Use Parameters and ReturnType when you only have typeof someFunction and want a standalone type, such as a props type derived from an imported helper.

One more in the same spirit: Readonly<T> marks every property read-only, so DEFAULTS.timeout = 1 fails with TS2540. Two caveats. It's compile-time only, so pair it with Object.freeze if a runtime write would be a bug. And it's shallow: nested objects stay mutable.

export const DEFAULTS: Readonly<Config> = Object.freeze({ timeout: 5000, retries: 3 });

I compiled all of this under strict TypeScript 7.0.2 and executed it on Node 22.23.3; it lives in the same runnable folder the rest of the guide uses, with two new must-fail/ files for the Promise trap and the read-only write.

Which Utility Types Should I Learn First?

Start with Partial and Pick. You'll use them on day one. Then add Omit and Record. Save Exclude and Extract for when you start working with complex union manipulation. If you're onboarding a junior developer onto a TypeScript codebase, this order roughly matches how often each utility type actually appears in a typical pull request queue: Partial and Pick show up almost daily, Omit and Record a few times a week, Exclude and Extract closer to once a month. Twenty-plus utility types exist in current TypeScript releases, but according to the TypeScript Handbook's own utility types page, the seven documented at the top of that reference page are the ones the compiler team considers foundational, everything past that list, ConstructorParameters, InstanceType, Awaited, exists for narrower metaprogramming cases you'll meet only when you need them.

Utility typeWhat it doesReach for it when
Partial<T>Makes every property optionalUpdate payloads, form drafts, config overrides
Required<T>Makes every property requiredValidating a config object after defaults are merged in
Pick<T, K>Keeps only the named keysNarrowing a wide model down to what one component needs
Omit<T, K>Drops the named keysStripping id or createdAt before a create call
Record<K, T>Maps a key union onto a value typeLookup tables and dictionaries keyed by a union
Exclude<T, U>Removes matching members from a unionFiltering null and undefined out of a union
Extract<T, U>Keeps only matching union membersPulling one variant out of a discriminated union
Parameters<F>The argument list of a function, as a tupleTyping a wrapper or a test double for an existing function
Awaited<ReturnType<F>>The value an async function resolves toTyping the result of a fetcher without a second interface
Readonly<T>Makes every property read-only (shallow)Frozen defaults and config constants

Don't try to memorize the full list. TypeScript 5.x ships around 20 utility types. Most of them are useful occasionally. The seven covered here cover 90% of real cases.

What Else Should You Read?

  • The TypeScript satisfies operator - checks a config object against a type without widening the literal types you still want to read back
  • TypeScript Generics: A Practical Guide for React Developers - utility types are built from generic mapped types; understanding generics helps you build your own utilities
  • TypeScript 7 (Project Corsa) guide - the native Go compiler type-checks deeply nested utility type chains 10x faster; the overview explains what changed at the July 2026 GA
  • TypeScript 7 migration - the step-by-step upgrade, worth reading before you lean into Pick, Omit, and conditional types at scale
  • Node.js 20 to 24 migration - @types/node ships updated utility-compatible types for new Node.js globals like ReadableStream and fetch; upgrading Node changes which types you need
  • React Hooks pitfalls - use Partial<T> and Pick<T, K> to type hook return values and state objects cleanly

Frequently Asked Questions

What is the difference between Pick and Omit?
Pick<T, K> creates a new type with only the keys you list. Omit<T, K> creates a new type with everything except the keys you list. Use Pick when you want a small subset; use Omit when it's easier to name what to remove.
When should I use Partial vs Required?
Use Partial<T> when building update payloads or form drafts where not every field is present. Use Required<T> when you need to enforce that every optional field is provided, such as after validation.
What is the difference between Exclude and Extract?
Exclude<T, U> removes members of U from a union T. Extract<T, U> keeps only the members that exist in both T and U. They are inverses of each other.
Why does ReturnType of my async function give me a Promise?
Because an async function does return a Promise, and ReturnType reports exactly that. To get the value inside it, wrap the result in Awaited: Awaited<ReturnType<typeof fetchUser>> is User, while ReturnType<typeof fetchUser> is Promise<User>. Reading .name off the Promise fails with error TS2339.
Can I combine utility types?
Yes. You can compose them freely: Partial<Pick<User, 'name' | 'email'>> creates a type with optional name and email fields only. Nesting utility types is a normal TypeScript pattern.