JP
JP Codes
Change theme
Development

The Ultimate Guide to TypeScript Utility Types

TypeScript's built-in utility types — Pick, Omit, Partial, Record, ReturnType, Awaited, and more — are type-level tools for expressing intent. Here's a practical walkthrough of all of them.

James Platt

James Platt

March 9, 2026 · 7 min read

7 min read
TypeScript code on a screen

TypeScript has a reputation for being powerful, expressive, and occasionally a little overwhelming. But one of the features that makes it truly shine is its utility types — built-in helpers that let you transform, filter, and manipulate types without rewriting everything from scratch.

This post gives you a clear, practical walkthrough of each utility type, why it exists, and when you should reach for it.

1. Object Property Utilities

Pick<Obj, Keys>

Creates a new type containing only the selected keys.

Create a new object type that contains only the specified keys from the original object.

Great for form fields, partial views, or API payloads where you only need a subset of a larger type.

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

Omit<Obj, Keys>

The opposite of Pick. Removes the specified keys and keeps everything else.

type UserWithoutSensitive = Omit<User, "age" | "name">;
// { id: number }

Partial<Obj>

Makes all properties optional. Useful for update operations where you only want to modify some properties.

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

Required<Obj>

The opposite of Partial. Makes all optional properties required. Useful when you need to ensure all properties are defined — for example, after validation.

Readonly<Obj>

Locks down all properties so they cannot be reassigned after creation. Useful for creating immutable data structures and ensuring data integrity. Works for arrays too:

type ReadonlyNames = Readonly<string[]>;
// readonly string[]

Record<Keys, Type>

Creates an object type with a fixed set of keys and a uniform value type. Perfect for creating dictionaries or maps with known keys.

type Role = "admin" | "user" | "guest";
type Permissions = Record<Role, string[]>;
// { admin: string[]; user: string[]; guest: string[] }

2. Function & Constructor Utilities

Parameters<Func>

Extracts a function's parameter types as a tuple. Useful for wrapping functions or building middleware.

type Params = Parameters<(name: string, age: number) => string>;
// [string, number]

ReturnType<Func>

Extracts the return type of a function. Perfect when you need to work with the return type of a function that uses implicit types — handy when you don't control the source.

function getUser() {
  return { id: 1, name: "James" };
}
type UserResult = ReturnType<typeof getUser>;
// { id: number; name: string }

ConstructorParameters<Class>

Same idea as Parameters, but for class constructors. Useful for factory functions, dependency injection, or any pattern where you instantiate classes dynamically.

InstanceType<Class>

Extracts the instance type produced by a constructor function. Most useful with abstract factory patterns — for concrete classes, you can just use the class type directly.

3. Union Type Utilities

Extract<Union, Members>

Keeps only the specified members of a union. Useful for creating subsets of union types when you know which variants you care about.

type Shape = "circle" | "square" | "triangle";
type RoundShapes = Extract<Shape, "circle">;
// "circle"

Exclude<Union, Members>

Removes the specified members. The opposite of Extract, and similar in concept to Omit for objects.

type FlatShapes = Exclude<Shape, "circle">;
// "square" | "triangle"

NonNullable<T>

Removes null and undefined from a type. Useful for type narrowing after you've validated that a value is defined — similar to Required, but specifically targets nullability.

type MaybeString = string | null | undefined;
type DefiniteString = NonNullable<MaybeString>;
// string

4. Promise & Async Utilities

Awaited<PromiseType>

Unwraps the resolved type of a Promise — including nested Promises, recursively. Perfect for working with async functions.

type Nested = Promise<Promise<number>>;
type Result = Awaited<Nested>;
// number

This is especially useful when chaining async utilities and you want to infer the final resolved value without manually unwrapping each layer.

5. String Transformation Utilities

These four utilities operate on string literal types — incredibly useful for API design, naming conventions, and code generation.

Utility Effect Use case
Uppercase<T>All characters uppercaseHTTP method literals, env keys
Lowercase<T>All characters lowercaseNormalising route segments
Capitalize<T>First character uppercasecamelCase → PascalCase
Uncapitalize<T>First character lowercasePascalCase → camelCase
type EventName = "click" | "focus" | "blur";
type HandlerName = `on${Capitalize<EventName>}`;
// "onClick" | "onFocus" | "onBlur"

Quick Reference

Here's the full set at a glance — grouped by the problem they solve:

Goal Reach for
Build a form modelPick
Update only part of an objectPartial
Enforce immutabilityReadonly
Map over a fixed set of keysRecord
Type an async function's resultReturnType / Awaited
Remove null / undefinedNonNullable
Normalise string unionsUppercase / Lowercase
Narrow a union to a subsetExtract / Exclude

Why These Utility Types Matter

TypeScript utility types are more than shortcuts — they're type-level tools for expressing intent. When you use Partial on an update payload, you're communicating that every field is optional by design, not by accident. When you use Readonly, you're documenting an immutability guarantee that the compiler will enforce.

Good types tell a story. Utility types let you tell it without repeating yourself.

They help you write cleaner, safer, and more maintainable TypeScript — without creating parallel type hierarchies or copy-pasting definitions that drift out of sync.

The next time you find yourself manually redefining a type that almost matches an existing one, check the utility types first. There's a good chance TypeScript already has exactly the helper you need.

Tags

James Platt

James Platt

Web Developer

James is a Microsoft-qualified C# .NET developer with extensive experience building robust, data-rich web applications. He writes about web development, software architecture, and best practices at JP Codes.

Read more about James

More articles