TypeScript Utility Types: The Complete Guide
A comprehensive guide to all built-in TypeScript utility types including Partial, Required, Pick, Omit, Record, Extract, Exclude, and more with practical examples.
What you'll learn
- ✓How every built-in TypeScript utility type works internally
- ✓When to use each utility type in real application code
- ✓How to combine utility types for complex transformations
- ✓How to build your own custom utility types
Prerequisites
- •TypeScript basics
- •Understanding of generics and mapped types
TypeScript ships with a set of built-in utility types that transform existing types into new ones. Instead of defining separate interfaces for every variation of your data, you can derive them from a single source of truth. This article covers every built-in utility type, explains how each one works internally, and shows when to use it.
Property Modifier Types
Partial<T>
Makes all properties of T optional:
interface User {
id: number;
name: string;
email: string;
role: "admin" | "user";
}
function updateUser(id: number, updates: Partial<User>): void {
// updates can have any combination of User fields
}
updateUser(1, { name: "Alice" });
updateUser(1, { email: "new@example.com", role: "admin" });
updateUser(1, {}); // also valid
How it works internally:
type Partial<T> = {
[P in keyof T]?: T[P];
};
The ? modifier makes every mapped property optional.
Required<T>
The opposite of Partial. Makes all properties required:
interface Config {
host?: string;
port?: number;
debug?: boolean;
}
function startServer(config: Required<Config>): void {
// All properties guaranteed to exist
console.log(`Starting on ${config.host}:${config.port}`);
}
startServer({ host: "localhost", port: 3000, debug: false });
// startServer({ host: "localhost" }); // Error: missing port and debug
Internal implementation:
type Required<T> = {
[P in keyof T]-?: T[P];
};
The -? modifier removes optionality.
Readonly<T>
Makes all properties read-only:
interface State {
count: number;
items: string[];
}
function createState(initial: State): Readonly<State> {
return Object.freeze(initial);
}
const state = createState({ count: 0, items: [] });
// state.count = 1; // Error: Cannot assign to 'count' because it is a read-only property
Note that Readonly is shallow. Nested objects are still mutable:
state.items.push("item"); // No error - the array itself is mutable
For deep immutability, you need a recursive type:
type DeepReadonly<T> = {
readonly [P in keyof T]: T[P] extends object ? DeepReadonly<T[P]> : T[P];
};
Property Selection Types
Pick<T, K>
Creates a type with only the specified properties:
interface User {
id: number;
name: string;
email: string;
password: string;
createdAt: Date;
}
type PublicUser = Pick<User, "id" | "name" | "email">;
function sanitizeUser(user: User): PublicUser {
return {
id: user.id,
name: user.name,
email: user.email,
};
}
Omit<T, K>
Creates a type with all properties except the specified ones:
type CreateUserInput = Omit<User, "id" | "createdAt">;
function createUser(input: CreateUserInput): User {
return {
...input,
id: generateId(),
createdAt: new Date(),
};
}
createUser({ name: "Alice", email: "alice@example.com", password: "secure123" });
Pick and Omit are complementary. Use Pick when you want a small subset. Use Omit when you want most properties minus a few.
Record<K, T>
Creates an object type with keys of type K and values of type T:
type UserRole = "admin" | "editor" | "viewer";
const rolePermissions: Record<UserRole, string[]> = {
admin: ["read", "write", "delete", "manage"],
editor: ["read", "write"],
viewer: ["read"],
};
Record is especially useful for lookup objects and dictionaries:
type HttpStatus = 200 | 201 | 400 | 401 | 404 | 500;
const statusMessages: Record<HttpStatus, string> = {
200: "OK",
201: "Created",
400: "Bad Request",
401: "Unauthorized",
404: "Not Found",
500: "Internal Server Error",
};
Internal implementation:
type Record<K extends keyof any, T> = {
[P in K]: T;
};
Union Manipulation Types
Extract<T, U>
Extracts from T the types that are assignable to U:
type AllEvents = "click" | "scroll" | "mousemove" | "keypress" | "keyup" | "keydown";
type MouseEvents = Extract<AllEvents, "click" | "scroll" | "mousemove">;
// "click" | "scroll" | "mousemove"
type KeyEvents = Extract<AllEvents, `key${string}`>;
// "keypress" | "keyup" | "keydown"
Exclude<T, U>
Removes from T the types that are assignable to U:
type NonKeyEvents = Exclude<AllEvents, `key${string}`>;
// "click" | "scroll" | "mousemove"
type NonNullable<T> = Exclude<T, null | undefined>;
// This is actually how NonNullable is implemented
Practical example for filtering union types:
type ApiEndpoint =
| { method: "GET"; path: string; response: unknown }
| { method: "POST"; path: string; body: unknown; response: unknown }
| { method: "DELETE"; path: string; response: void };
type WriteMethods = Extract<ApiEndpoint, { method: "POST" | "DELETE" }>;
NonNullable<T>
Removes null and undefined from a type:
type MaybeUser = User | null | undefined;
function processUser(user: NonNullable<MaybeUser>): void {
console.log(user.name); // safe, no null check needed
}
function getUser(id: number): MaybeUser {
return users.get(id) ?? null;
}
const user = getUser(1);
if (user) {
processUser(user); // type narrows to NonNullable<MaybeUser>
}
Function Types
ReturnType<T>
Extracts the return type of a function:
function createUser(name: string, email: string) {
return {
id: crypto.randomUUID(),
name,
email,
createdAt: new Date(),
};
}
type User = ReturnType<typeof createUser>;
// { id: string; name: string; email: string; createdAt: Date }
This is invaluable when you want to type something based on what a function returns without maintaining a separate interface:
async function fetchDashboardData() {
const [users, orders, metrics] = await Promise.all([
fetchUsers(),
fetchOrders(),
fetchMetrics(),
]);
return { users, orders, metrics };
}
type DashboardData = Awaited<ReturnType<typeof fetchDashboardData>>;
Parameters<T>
Extracts the parameter types as a tuple:
function sendEmail(to: string, subject: string, body: string, cc?: string[]): void {
// ...
}
type SendEmailParams = Parameters<typeof sendEmail>;
// [to: string, subject: string, body: string, cc?: string[]]
function queueEmail(...args: Parameters<typeof sendEmail>): void {
emailQueue.push(args);
}
ConstructorParameters<T>
Like Parameters but for constructor functions:
class HttpClient {
constructor(
public baseUrl: string,
public timeout: number = 5000,
public headers: Record<string, string> = {}
) {}
}
type ClientConfig = ConstructorParameters<typeof HttpClient>;
// [baseUrl: string, timeout?: number, headers?: Record<string, string>]
function createClient(...args: ClientConfig): HttpClient {
return new HttpClient(...args);
}
InstanceType<T>
Gets the instance type of a constructor function:
class UserRepository {
findAll(): User[] { return []; }
findById(id: number): User | null { return null; }
}
type Repo = InstanceType<typeof UserRepository>;
// UserRepository
Awaited<T>
Unwraps the resolved type of a Promise, recursively:
type A = Awaited<Promise<string>>; // string
type B = Awaited<Promise<Promise<number>>>; // number
type C = Awaited<string | Promise<number>>; // string | number
This is what makes async/await type inference work correctly:
async function getData(): Promise<{ items: string[] }> {
return { items: ["a", "b"] };
}
type Data = Awaited<ReturnType<typeof getData>>;
// { items: string[] }
String Manipulation Types
TypeScript includes types that transform string literal types:
type Upper = Uppercase<"hello">; // "HELLO"
type Lower = Lowercase<"HELLO">; // "hello"
type Cap = Capitalize<"hello">; // "Hello"
type Uncap = Uncapitalize<"Hello">; // "hello"
These are most useful with template literal types:
type EventName = "click" | "scroll" | "resize";
type EventHandler = `on${Capitalize<EventName>}`;
// "onClick" | "onScroll" | "onResize"
type EventCallback<T extends EventName> = {
[K in T as `on${Capitalize<K>}`]: (event: Event) => void;
};
type ClickHandler = EventCallback<"click">;
// { onClick: (event: Event) => void }
Combining Utility Types
The real power comes from composing utility types:
interface User {
id: number;
name: string;
email: string;
password: string;
role: "admin" | "user";
createdAt: Date;
updatedAt: Date;
}
// For creating new users: no id, no timestamps
type CreateUser = Omit<User, "id" | "createdAt" | "updatedAt">;
// For updating: all fields optional except id
type UpdateUser = Partial<Omit<User, "id">> & Pick<User, "id">;
// For API responses: no password
type UserResponse = Omit<User, "password">;
// For lists: just the essentials
type UserSummary = Pick<User, "id" | "name" | "role">;
// For admin views: everything read-only
type AdminUserView = Readonly<UserResponse>;
Building Custom Utility Types
Once you understand how the built-in types work, you can build your own:
// Make specific properties optional
type PartialBy<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;
type CreateUser = PartialBy<User, "role" | "email">;
// id, name, password required; role, email optional
// Make specific properties required
type RequiredBy<T, K extends keyof T> = T & Required<Pick<T, K>>;
// Get only the properties that are functions
type Methods<T> = {
[K in keyof T as T[K] extends Function ? K : never]: T[K];
};
// Get only non-function properties
type DataFields<T> = {
[K in keyof T as T[K] extends Function ? never : K]: T[K];
};
// Make all properties mutable (remove readonly)
type Mutable<T> = {
-readonly [P in keyof T]: T[P];
};
Wrapping Up
TypeScript’s utility types are mapped types and conditional types packaged for common use cases. Partial, Required, and Readonly modify property attributes. Pick and Omit select subsets of properties. Record creates object types from key and value types. Extract and Exclude filter union types. ReturnType, Parameters, and Awaited extract information from function and promise types. Combining these types lets you derive every variation of your data shapes from a single source definition, keeping your types DRY and consistent.
Related articles
- TypeScript TypeScript infer Keyword Explained
Master the infer keyword in TypeScript conditional types. Learn how to extract parts of complex types, build utility helpers, and write expressive generics.
- TypeScript TypeScript Mapped Types Deep Dive
A practical tour of mapped types in TypeScript, including key remapping, modifiers, and patterns that build powerful generic utilities.
- TypeScript TypeScript Recursive Types Tutorial
Build recursive types in TypeScript: deep readonly, JSON, paths, and tuple manipulation. Learn how to write recursion that the compiler can actually evaluate.
- TypeScript TypeScript Generics: Advanced Patterns and Constraints
Master advanced TypeScript generics patterns including conditional constraints, recursive types, generic factories, and type-safe builder patterns with examples.