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 uppercase | HTTP method literals, env keys |
| Lowercase<T> | All characters lowercase | Normalising route segments |
| Capitalize<T> | First character uppercase | camelCase → PascalCase |
| Uncapitalize<T> | First character lowercase | PascalCase → 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 model | Pick |
| Update only part of an object | Partial |
| Enforce immutability | Readonly |
| Map over a fixed set of keys | Record |
| Type an async function's result | ReturnType / Awaited |
| Remove null / undefined | NonNullable |
| Normalise string unions | Uppercase / Lowercase |
| Narrow a union to a subset | Extract / 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