Utility Types in TypeScript
Master TypeScript's built-in utility types: Partial, Required, Readonly, Pick, Omit, Record, ReturnType, Awaited, and more.
Partial
Partial<T> makes all properties of T optional by adding ? to every field. This is extremely useful for update and patch operations where you only want to send the fields that changed — without it, you’d need a separate hand-written type for every partial update.
interface User {
id: number;
name: string;
email: string;
role: string;
}
type UserUpdate = Partial<User>;
// { id?: number; name?: string; email?: string; role?: string }
async function updateUser(id: number, changes: Partial<User>): Promise<User> {
const existing = await findUser(id);
return { ...existing, ...changes }; // merge only the provided fields
}
updateUser(1, { name: "New Name" }); // fine — only updating name
updateUser(1, { name: "New", role: "admin" }); // fine — updating two fields
Required
Required<T> is the inverse of Partial — it removes ? from every property, making all fields mandatory. It’s most useful after a validation step, where you can assert that all optional config fields have been filled in with defaults.
interface Config {
apiUrl?: string;
timeout?: number;
retries?: number;
}
type FullConfig = Required<Config>;
// { apiUrl: string; timeout: number; retries: number }
// After this function, callers can treat all fields as definitely present
function validateConfig(raw: Config): FullConfig {
if (!raw.apiUrl) throw new Error("apiUrl required");
return {
apiUrl: raw.apiUrl,
timeout: raw.timeout ?? 5000,
retries: raw.retries ?? 3,
};
}
Readonly
Readonly<T> adds the readonly modifier to every property, preventing mutation after the object is created. This is useful for configuration objects, function parameters you don’t want to accidentally modify, and immutable value types.
interface Point {
x: number;
y: number;
}
const origin: Readonly<Point> = { x: 0, y: 0 };
origin.x = 1; // Error: Cannot assign to 'x' because it is a read-only property
// Particularly useful for function parameters — signals you won't mutate the input
function translate(point: Readonly<Point>, dx: number, dy: number): Point {
// point.x += dx; // Error — would mutate the input
return { x: point.x + dx, y: point.y + dy }; // return a new point instead
}
Note: Readonly is shallow. Nested objects remain mutable — for deep immutability you need a recursive mapped type.
Pick
Pick<T, K> creates a new type containing only the properties you specify. This is the right tool when you need a subset of a larger type — for example, a public API response that omits sensitive fields, or a form that only edits certain properties.
interface User {
id: number;
name: string;
email: string;
password: string;
role: string;
createdAt: Date;
}
// Public profile — expose only safe fields, never password or timestamps
type PublicUser = Pick<User, "id" | "name" | "role">;
// { id: number; name: string; role: string }
// Login form — only needs these two fields
type LoginCredentials = Pick<User, "email" | "password">;
function getPublicProfile(user: User): PublicUser {
const { id, name, role } = user;
return { id, name, role }; // TypeScript verifies this matches PublicUser
}
Omit
Omit<T, K> creates a type with the specified keys removed — the inverse of Pick. Use it when it’s easier to list the fields you want to exclude rather than the ones you want to keep. A common pattern is omitting server-generated fields from creation payloads.
// Creation payload — omit server-generated fields the client shouldn't provide
type CreateUserDto = Omit<User, "id" | "createdAt">;
// Safe user response — never send the password to the client
type SafeUser = Omit<User, "password">;
async function createUser(data: CreateUserDto): Promise<User> {
const id = generateId();
const createdAt = new Date();
return { ...data, id, createdAt }; // server fills in the omitted fields
}
Record
Record<K, V> creates an object type where all keys are of type K and all values are of type V. It’s more expressive than a plain index signature because it can accept a union type as K, which means TypeScript verifies every key in the union is present.
// A string dictionary — any string key, string value
type StringMap = Record<string, string>;
// Exhaustive map — every role must have an entry
type RolePermissions = Record<"admin" | "editor" | "viewer", string[]>;
const permissions: RolePermissions = {
admin: ["read", "write", "delete"],
editor: ["read", "write"],
viewer: ["read"],
// Omitting any role would be a TypeScript error
};
// Mapping status values to display config
type StatusConfig = Record<
"idle" | "loading" | "success" | "error",
{ label: string; color: string }
>;
const statusConfig: StatusConfig = {
idle: { label: "Idle", color: "gray" },
loading: { label: "Loading...", color: "blue" },
success: { label: "Done", color: "green" },
error: { label: "Failed", color: "red" },
};
Exclude and Extract
Exclude<T, U> removes members from a union type — it filters out any member of T that is assignable to U. This is useful for narrowing a union after you’ve handled certain cases.
type Status = "active" | "inactive" | "banned" | "deleted";
// Remove the moderated statuses — only regular lifecycle statuses remain
type ActiveStatus = Exclude<Status, "banned" | "deleted">;
// "active" | "inactive"
type Primitive = string | number | boolean | null | undefined;
type NonNullPrimitive = Exclude<Primitive, null | undefined>;
// string | number | boolean
Extract<T, U> is the opposite — it keeps only the members of T that are assignable to U:
// Keep only strings from the union
type Strings = Extract<string | number | boolean, string>;
// string
type Shapes = "circle" | "square" | "triangle" | "polygon";
type Simple = Extract<Shapes, "circle" | "square">;
// "circle" | "square"
NonNullable
NonNullable<T> removes null and undefined from a type. It’s the typed equivalent of asserting “I’ve already checked this isn’t null.” Use it after a null check to get a cleaner type for the remainder of a function.
type MaybeString = string | null | undefined;
type DefiniteString = NonNullable<MaybeString>;
// string
// Useful for assertion helpers that throw instead of returning null
function assertDefined<T>(value: T | null | undefined): NonNullable<T> {
if (value == null) throw new Error("Expected a value, got null or undefined");
return value as NonNullable<T>;
}
ReturnType
ReturnType<T> extracts the return type of a function type. It’s invaluable when you need to type a variable that holds the result of a function but don’t want to repeat the return type manually — especially when the function’s return type is complex or inferred.
function getUser() {
return { id: 1, name: "Alice", email: "[email protected]" };
}
// Derive the type from the function — stays in sync automatically if the function changes
type User = ReturnType<typeof getUser>;
// { id: number; name: string; email: string }
// Useful for factory functions that return complex objects
function createStore() {
let count = 0;
return {
increment() { count++; },
decrement() { count--; },
getCount() { return count; },
};
}
type Store = ReturnType<typeof createStore>;
// { increment: () => void; decrement: () => void; getCount: () => number }
Parameters
Parameters<T> extracts the parameter types of a function type as a tuple. It’s useful when you need to capture, forward, or partially apply arguments while keeping them typed.
function connect(host: string, port: number, secure: boolean): void {}
type ConnectArgs = Parameters<typeof connect>;
// [host: string, port: number, secure: boolean]
// Capture and spread as arguments later
const args: ConnectArgs = ["localhost", 3000, false];
connect(...args); // fully typed — TypeScript verifies the spread matches the signature
Awaited
Awaited<T> unwraps Promise types recursively, giving you the value type that a Promise resolves to. It handles nested Promises and non-Promise values gracefully — making it useful for typing the result of async operations.
type A = Awaited<Promise<string>>; // string
type B = Awaited<Promise<Promise<number>>>; // number — unwraps both levels
type C = Awaited<string>; // string — non-promise passes through
async function fetchUser(): Promise<User> { /* ... */ }
// Derive the resolved type without unwrapping Promise manually
type FetchedUser = Awaited<ReturnType<typeof fetchUser>>;
// User
InstanceType
InstanceType<T> extracts the instance type produced by a class constructor. It’s useful in factory patterns and dependency injection where you work with constructor references rather than instances directly.
class DatabaseConnection {
query(sql: string): Promise<unknown[]> { return Promise.resolve([]); }
close(): void {}
}
type DBConn = InstanceType<typeof DatabaseConnection>;
// DatabaseConnection — same as using the class name as a type, but works generically
// A factory that accepts any constructor and types the instance correctly
function withConnection<T>(
Ctor: new () => T,
fn: (conn: T) => Promise<void>
): Promise<void> {
const conn = new Ctor();
return fn(conn);
}
Combining Utility Types
The real power of utility types comes from composing them. You can derive an entire family of related types from a single source-of-truth interface, keeping everything in sync as the base type evolves.
interface User {
id: number;
name: string;
email: string;
password: string;
role: "admin" | "user";
createdAt: Date;
updatedAt: Date;
}
// Create: omit server-generated fields — client provides the rest
type CreateUser = Omit<User, "id" | "createdAt" | "updatedAt">;
// Update: all fields optional, server fields still excluded
type UpdateUser = Partial<Omit<User, "id" | "createdAt" | "updatedAt">>;
// Response: never expose password, make it readonly so callers don't mutate it
type UserResponse = Readonly<Omit<User, "password">>;
// List item: minimal data for table rows — only what the UI needs
type UserListItem = Pick<User, "id" | "name" | "email" | "role">;
This pattern — deriving several types from a single source of truth — is one of the most practical applications of TypeScript utility types in production codebases. When the User interface changes, all derived types update automatically.