5.3 Built-in Utility Types
What Are Utility Types?
TypeScript ships with a set of utility types that encode common type-transformation patterns. They are all generic and derive a new type from an existing one.
interface User {
id: number;
name: string;
email: string;
password: string;
role: "admin" | "user";
createdAt: Date;
}
// Deriving various types from the same source
type PartialUser = Partial<User>; // all fields optional
type PublicUser = Omit<User, "password">; // password removed
type UserFormData = Pick<User, "name" | "email" | "password">; // subset
type ReadonlyUser = Readonly<User>; // all fields read-only
With utility types you avoid duplicating type definitions and manage everything from one place while producing any variation you need.
Partial<T>
Makes every property optional (?).
interface Post {
id: number;
title: string;
content: string;
published: boolean;
tags: string[];
}
// All fields become optional
type PartialPost = Partial<Post>;
// {
// id?: number;
// title?: string;
// content?: string;
// published?: boolean;
// tags?: string[];
// }
// Useful in update functions
function updatePost(id: number, updates: Partial<Post>): Post {
// Fetch existing post from DB and merge updates
const existing = getPostFromDb(id);
return { ...existing, ...updates };
}
// Only some fields need to be supplied
updatePost(1, { title: "New Title" }); // OK
updatePost(1, { title: "New Title", published: true }); // OK
updatePost(1, { nonExistent: "value" }); // Error
Implementing Partial
// How TypeScript implements it internally
type MyPartial<T> = {
[K in keyof T]?: T[K];
};
// Test
type MyPartialPost = MyPartial<Post>;
// Same result as Partial<Post>
Required<T>
Makes every property required. Uses the -? modifier to strip optionality.
interface Config {
host?: string;
port?: number;
debug?: boolean;
timeout?: number;
}
// All fields become required
type StrictConfig = Required<Config>;
// {
// host: string;
// port: number;
// debug: boolean;
// timeout: number;
// }
// Merge defaults with user config and return a complete config
const DEFAULT_CONFIG: Required<Config> = {
host: "localhost",
port: 3000,
debug: false,
timeout: 5000,
};
function resolveConfig(userConfig: Config): Required<Config> {
return { ...DEFAULT_CONFIG, ...userConfig };
}
const config = resolveConfig({ port: 8080 });
config.host; // string (never undefined)
config.port; // 8080
config.debug; // false
config.timeout; // 5000
Implementing Required
type MyRequired<T> = {
[K in keyof T]-?: T[K]; // -? removes the optional modifier
};
Readonly<T>
Makes every property readonly, preventing mutation.
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
// Immutable configuration object
const APP_CONFIG = Object.freeze({
apiUrl: "https://api.example.com",
version: "1.0.0",
maxRetries: 3,
}) as Readonly<{
apiUrl: string;
version: string;
maxRetries: number;
}>;
// Declare function parameters readonly to prevent accidental mutation
function processUsers(users: Readonly<User[]>): string[] {
// users.push(...) // Error: push is not available on a Readonly array
return users.map((u) => u.name);
}
Implementing Readonly
type MyReadonly<T> = {
readonly [K in keyof T]: T[K];
};
Pick<T, K>
Creates a new type containing only the keys K from type T.
interface Article {
id: number;
title: string;
content: string;
summary: string;
author: string;
publishedAt: Date;
viewCount: number;
tags: string[];
}
// List view — exclude the heavy content field
type ArticleListItem = Pick<Article, "id" | "title" | "summary" | "publishedAt" | "author">;
// {
// id: number;
// title: string;
// summary: string;
// publishedAt: Date;
// author: string;
// }
// SEO metadata
type ArticleMeta = Pick<Article, "title" | "summary" | "tags">;
// Usage
function renderArticleCard(article: ArticleListItem): string {
return `<h2>${article.title}</h2><p>${article.summary}</p>`;
}
Implementing Pick
type MyPick<T, K extends keyof T> = {
[P in K]: T[P];
};
// Test
type MyArticleListItem = MyPick<Article, "id" | "title" | "summary">;
Omit<T, K>
Creates a new type that excludes the keys K from type T.
interface User {
id: number;
name: string;
email: string;
password: string;
createdAt: Date;
}
// Exclude sensitive fields
type PublicUser = Omit<User, "password">;
// {
// id: number;
// name: string;
// email: string;
// createdAt: Date;
// }
// id and createdAt are generated server-side on creation
type CreateUserDto = Omit<User, "id" | "createdAt">;
// {
// name: string;
// email: string;
// password: string;
// }
function createUser(dto: CreateUserDto): User {
return {
...dto,
id: Math.random(),
createdAt: new Date(),
};
}
Implementing Omit
// Omit is implemented as Pick + Exclude
type MyOmit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;
// Or directly with a mapped type
type MyOmit2<T, K extends keyof T> = {
[P in keyof T as P extends K ? never : P]: T[P];
};
Exclude<T, U>
Removes from union type T any member that is assignable to U.
type Status = "pending" | "active" | "inactive" | "deleted";
// Active statuses — exclude "deleted"
type ActiveStatus = Exclude<Status, "deleted">;
// "pending" | "active" | "inactive"
// Remove multiple members
type WorkingStatus = Exclude<Status, "inactive" | "deleted">;
// "pending" | "active"
// Remove null/undefined (similar to NonNullable)
type NonNull = Exclude<string | null | undefined, null | undefined>;
// string
// Remove function types
type PrimitiveOnly = Exclude<string | number | (() => void), Function>;
// string | number
Implementing Exclude
// Implemented with a distributive conditional type
type MyExclude<T, U> = T extends U ? never : T;
// How it works (distribution)
// MyExclude<"a" | "b" | "c", "b">
// = MyExclude<"a", "b"> | MyExclude<"b", "b"> | MyExclude<"c", "b">
// = "a" | never | "c"
// = "a" | "c"
Extract<T, U>
Extracts from union type T only the members assignable to U. The opposite of Exclude.
type AllTypes = string | number | boolean | null | undefined | object;
// Extract only primitives
type Primitives = Extract<AllTypes, string | number | boolean>;
// string | number | boolean
// Extract common members
type A = "admin" | "user" | "moderator";
type B = "user" | "moderator" | "guest";
type Common = Extract<A, B>;
// "user" | "moderator"
// Extract types that match a specific structure
type Actions =
| { type: "INCREMENT"; payload: number }
| { type: "DECREMENT"; payload: number }
| { type: "RESET" }
| { type: "SET_USER"; payload: string };
type PayloadActions = Extract<Actions, { payload: unknown }>;
// { type: "INCREMENT"; payload: number }
// | { type: "DECREMENT"; payload: number }
// | { type: "SET_USER"; payload: string }
Implementing Extract
type MyExtract<T, U> = T extends U ? T : never;
NonNullable<T>
Removes null and undefined from type T.
type MaybeString = string | null | undefined;
type DefiniteString = NonNullable<MaybeString>; // string
type MaybeUser = User | null | undefined;
type DefiniteUser = NonNullable<MaybeUser>; // User
// Useful for processing API responses
function assertDefined<T>(
value: T | null | undefined,
message: string
): NonNullable<T> {
if (value === null || value === undefined) {
throw new Error(message);
}
return value as NonNullable<T>;
}
const maybeUser: User | null = getUserOrNull();
const user = assertDefined(maybeUser, "User not found.");
// user is typed as User (not null)
Implementing NonNullable
type MyNonNullable<T> = T extends null | undefined ? never : T;
// In TypeScript 4.8+ it can also be expressed as T & {}
type MyNonNullable2<T> = T & {};
Record<K, V>
Creates an object type with keys of type K and values of type V.
// Basic usage
type UserMap = Record<number, User>;
type RolePermissions = Record<"admin" | "user" | "moderator", string[]>;
type Cache = Record<string, unknown>;
// Combined with an enum
enum HttpMethod {
GET = "GET",
POST = "POST",
PUT = "PUT",
DELETE = "DELETE",
PATCH = "PATCH",
}
type MethodHandlers = Record<HttpMethod, (req: Request) => Response>;
// Status color map
type StatusColor = Record<
"success" | "error" | "warning" | "info",
{ bg: string; text: string; border: string }
>;
const statusColors: StatusColor = {
success: { bg: "#d4edda", text: "#155724", border: "#c3e6cb" },
error: { bg: "#f8d7da", text: "#721c24", border: "#f5c6cb" },
warning: { bg: "#fff3cd", text: "#856404", border: "#ffeeba" },
info: { bg: "#d1ecf1", text: "#0c5460", border: "#bee5eb" },
};
Implementing Record
type MyRecord<K extends string | number | symbol, V> = {
[P in K]: V;
};
ReturnType<T> and Parameters<T>
Extract the return type and parameter types from a function type.
// ReturnType
function fetchUser(id: number): Promise<User> {
return fetch(`/api/users/${id}`).then((r) => r.json());
}
type FetchUserReturn = ReturnType<typeof fetchUser>;
// Promise<User>
function createRange(start: number, end: number): number[] {
return Array.from({ length: end - start }, (_, i) => start + i);
}
type RangeResult = ReturnType<typeof createRange>; // number[]
// Parameters
type FetchUserParams = Parameters<typeof fetchUser>;
// [id: number]
type CreateRangeParams = Parameters<typeof createRange>;
// [start: number, end: number]
// Use case: preserving types when wrapping a function
function memoize<T extends (...args: any[]) => any>(
fn: T
): (...args: Parameters<T>) => ReturnType<T> {
const cache = new Map<string, ReturnType<T>>();
return (...args: Parameters<T>): ReturnType<T> => {
const key = JSON.stringify(args);
if (cache.has(key)) {
return cache.get(key)!;
}
const result = fn(...args);
cache.set(key, result);
return result;
};
}
const memoizedRange = memoize(createRange);
memoizedRange(1, 10); // number[] — type is preserved
Other Function-Related Utilities
// ConstructorParameters<T>: extract constructor parameter types
class Vector {
constructor(public x: number, public y: number, public z: number) {}
}
type VectorArgs = ConstructorParameters<typeof Vector>;
// [x: number, y: number, z: number]
// InstanceType<T>: extract the instance type of a class
type VectorInstance = InstanceType<typeof Vector>;
// Vector
// Awaited<T>: unwrap a Promise (TypeScript 4.5+)
type UserData = Awaited<Promise<User>>; // User
type NestedData = Awaited<Promise<Promise<string>>>; // string
Implementing Utility Types (Learning Exercise)
// Implementing all utility types seen so far
namespace MyUtility {
export type Partial<T> = { [K in keyof T]?: T[K] };
export type Required<T> = { [K in keyof T]-?: T[K] };
export type Readonly<T> = { readonly [K in keyof T]: T[K] };
export type Pick<T, K extends keyof T> = { [P in K]: T[P] };
export type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;
export type Exclude<T, U> = T extends U ? never : T;
export type Extract<T, U> = T extends U ? T : never;
export type NonNullable<T> = T extends null | undefined ? never : T;
export type Record<K extends keyof any, V> = { [P in K]: V };
export type ReturnType<T extends (...args: any) => any> =
T extends (...args: any) => infer R ? R : never;
export type Parameters<T extends (...args: any) => any> =
T extends (...args: infer P) => any ? P : never;
}
Practical Examples
DTO Transformation Pattern
// Domain model
interface UserEntity {
id: number;
name: string;
email: string;
passwordHash: string;
salt: string;
createdAt: Date;
updatedAt: Date;
deletedAt: Date | null;
}
// Response DTO: strip sensitive fields
type UserResponseDto = Omit<UserEntity, "passwordHash" | "salt" | "deletedAt">;
// Create DTO: strip server-generated fields, add plain password
type CreateUserDto = Omit<UserEntity, "id" | "passwordHash" | "salt" | "createdAt" | "updatedAt" | "deletedAt"> & {
password: string;
passwordConfirm: string;
};
// Update DTO: id is required, everything else is optional
type UpdateUserDto = Partial<Omit<CreateUserDto, "password" | "passwordConfirm">> & {
id: number;
};
// Usage
const createDto: CreateUserDto = {
name: "Alice",
email: "alice@example.com",
password: "secure123",
passwordConfirm: "secure123",
};
const updateDto: UpdateUserDto = {
id: 1,
name: "Alice Updated", // only name is updated
};
Config Object Merge Pattern
// Safely merge default config with user config
interface ServerConfig {
host: string;
port: number;
ssl: boolean;
timeout: number;
maxConnections: number;
cors: {
origin: string[];
credentials: boolean;
};
}
type UserServerConfig = Partial<ServerConfig>;
const defaults: Required<ServerConfig> = {
host: "localhost",
port: 3000,
ssl: false,
timeout: 30000,
maxConnections: 100,
cors: {
origin: ["*"],
credentials: false,
},
};
function createServerConfig(userConfig: UserServerConfig): Required<ServerConfig> {
return {
...defaults,
...userConfig,
cors: {
...defaults.cors,
...(userConfig.cors ?? {}),
},
};
}
const config = createServerConfig({
port: 8080,
ssl: true,
});
// host: "localhost", port: 8080, ssl: true, ...rest are defaults
Update Payload Type
// Safely update any entity
type UpdatePayload<T extends { id: number }> = Pick<T, "id"> & Partial<Omit<T, "id">>;
interface Product {
id: number;
name: string;
price: number;
stock: number;
category: string;
}
type UpdateProductPayload = UpdatePayload<Product>;
// { id: number; name?: string; price?: number; stock?: number; category?: string; }
function updateProduct(payload: UpdateProductPayload): Promise<Product> {
return fetch(`/api/products/${payload.id}`, {
method: "PATCH",
body: JSON.stringify(payload),
}).then((r) => r.json());
}
updateProduct({ id: 1, price: 29.99 }); // update price only
updateProduct({ id: 2, stock: 0, name: "Out of Stock" }); // update stock and name
updateProduct({ id: 3 }); // id only (no changes)
Pro Tips
Combining Utility Types
// Pattern 1: Partial + Required to make only some fields required
type PartialExcept<T, K extends keyof T> = Partial<T> & Required<Pick<T, K>>;
interface Form {
username: string;
email: string;
bio?: string;
avatar?: string;
website?: string;
}
// username and email are required, everything else is optional
type CreateProfileForm = PartialExcept<Form, "username" | "email">;
const form1: CreateProfileForm = { username: "alice", email: "a@b.com" }; // OK
const form2: CreateProfileForm = { email: "a@b.com" }; // Error: username required
// Pattern 2: Omit + Record for type replacement
type ReplaceField<T, K extends keyof T, V> = Omit<T, K> & Record<K, V>;
type UserWithStringId = ReplaceField<User, "id", string>;
// id is now string; everything else stays the same
// Pattern 3: Nested utilities
type DeepReadonly<T> = {
readonly [K in keyof T]: T[K] extends object ? DeepReadonly<T[K]> : T[K];
};
const config2: DeepReadonly<ServerConfig> = {
host: "localhost",
port: 3000,
ssl: false,
timeout: 30000,
maxConnections: 100,
cors: { origin: ["*"], credentials: false },
};
config2.cors.origin = []; // Error: readonly
Implementing DeepPartial
// Make every property optional at all nesting levels
type DeepPartial<T> = T extends object
? { [K in keyof T]?: DeepPartial<T[K]> }
: T;
interface AppState {
user: {
profile: {
name: string;
avatar: string;
};
settings: {
theme: "light" | "dark";
language: string;
notifications: {
email: boolean;
push: boolean;
};
};
};
posts: {
list: Post[];
selectedId: number | null;
};
}
// Deeply nested structures can now be partially updated
type PartialAppState = DeepPartial<AppState>;
const patch: PartialAppState = {
user: {
settings: {
notifications: {
email: false, // turn off email notifications only
},
},
},
};
Designing API Layer Types with Utility Types
// Central pattern for managing all API types in one place
interface UserEntity {
id: number;
name: string;
email: string;
passwordHash: string;
createdAt: Date;
}
// Derive all API layer types from a single source
const UserTypes = {
Response: {} as Omit<UserEntity, "passwordHash">,
Create: {} as Omit<UserEntity, "id" | "passwordHash" | "createdAt"> & { password: string },
Update: {} as Partial<Omit<UserEntity, "id" | "passwordHash" | "createdAt">> & { id: number },
List: {} as Pick<UserEntity, "id" | "name" | "email">[],
};
type UserResponse = typeof UserTypes.Response;
type CreateUser = typeof UserTypes.Create;
type UpdateUser = typeof UserTypes.Update;
type UserList = typeof UserTypes.List;
Summary Table
| Utility Type | Description | Internal Implementation |
|---|---|---|
Partial<T> | All properties optional | { [K in keyof T]?: T[K] } |
Required<T> | All properties required | { [K in keyof T]-?: T[K] } |
Readonly<T> | All properties read-only | { readonly [K in keyof T]: T[K] } |
Pick<T, K> | Select a subset of properties | { [P in K]: T[P] } |
Omit<T, K> | Exclude a subset of properties | Pick<T, Exclude<keyof T, K>> |
Exclude<T, U> | Remove types from a union | T extends U ? never : T |
Extract<T, U> | Extract types from a union | T extends U ? T : never |
NonNullable<T> | Remove null/undefined | T extends null | undefined ? never : T |
Record<K, V> | Key-value map type | { [P in K]: V } |
ReturnType<T> | Extract function return type | Conditional type + infer |
Parameters<T> | Extract function parameter types | Conditional type + infer |
Awaited<T> | Unwrap Promise type | Recursive conditional type |
Up Next...
Section 5.4 dives deep into Conditional Types. You will learn the T extends U ? X : Y syntax, understand distributive conditional types that automatically fan out over union members, master the infer keyword for extracting types inside conditions, and directly implement complex utility types using conditional types.