TypeScript Utility Types
TypeScript provides a collection of built-in utility types that help transform and manipulate existing types. These utility types enable powerful type transformations without having to manually redefine interfaces, making your code more maintainable and reducing duplication.
Understanding and effectively using utility types is essential for advanced TypeScript development, as they provide elegant solutions for common type manipulation scenarios.
Basic Utility Types
Partial<T>
Makes all properties of type T optional.
interface User {
id: number;
name: string;
email: string;
age: number;
}
type PartialUser = Partial<User>;
// Equivalent to:
// {
// id?: number;
// name?: string;
// email?: string;
// age?: number;
// }
function updateUser(id: number, updates: PartialUser): User {
// Implementation would merge updates with existing user
return {} as User;
}
// Usage
updateUser(1, { name: "New Name" }); // Only name is required
updateUser(1, { email: "[email protected]", age: 30 }); // Multiple optional fields
Required<T>
Makes all properties of type T required.
interface Config {
apiUrl?: string;
timeout?: number;
retries?: number;
debug?: boolean;
}
type RequiredConfig = Required<Config>;
// Equivalent to:
// {
// apiUrl: string;
// timeout: number;
// retries: number;
// debug: boolean;
// }
function initializeApp(config: RequiredConfig): void {
// All properties are guaranteed to be present
console.log(`API URL: ${config.apiUrl}`);
console.log(`Timeout: ${config.timeout}ms`);
}
Readonly<T>
Makes all properties of type T readonly.
interface Point {
x: number;
y: number;
}
type ReadonlyPoint = Readonly<Point>;
// Equivalent to:
// {
// readonly x: number;
// readonly y: number;
// }
function createOrigin(): ReadonlyPoint {
return { x: 0, y: 0 };
}
const origin = createOrigin();
// origin.x = 1; // Error: Cannot assign to 'x' because it is a read-only property
Pick<T, K>
Creates a type by picking specific properties K from type T.
interface User {
id: number;
name: string;
email: string;
password: string;
createdAt: Date;
updatedAt: Date;
}
type PublicUser = Pick<User, 'id' | 'name' | 'email'>;
// Equivalent to:
// {
// id: number;
// name: string;
// email: string;
// }
type UserTimestamps = Pick<User, 'createdAt' | 'updatedAt'>;
// {
// createdAt: Date;
// updatedAt: Date;
// }
function getPublicUserInfo(user: User): PublicUser {
return {
id: user.id,
name: user.name,
email: user.email
};
}
Omit<T, K>
Creates a type by omitting specific properties K from type T.
type UserWithoutPassword = Omit<User, 'password'>;
// Equivalent to:
// {
// id: number;
// name: string;
// email: string;
// createdAt: Date;
// updatedAt: Date;
// }
type UserInput = Omit<User, 'id' | 'createdAt' | 'updatedAt'>;
// {
// name: string;
// email: string;
// password: string;
// }
function createUser(input: UserInput): User {
return {
...input,
id: Date.now(),
createdAt: new Date(),
updatedAt: new Date()
};
}
Advanced Utility Types
Record<K, T>
Creates a type with keys of type K and values of type T.
type Status = 'pending' | 'approved' | 'rejected';
type StatusConfig = Record<Status, { color: string; message: string }>;
const statusConfigs: StatusConfig = {
pending: { color: 'yellow', message: 'Waiting for approval' },
approved: { color: 'green', message: 'Request approved' },
rejected: { color: 'red', message: 'Request rejected' }
};
// Record with string keys
type UserRoles = Record<string, string[]>;
const roles: UserRoles = {
admin: ['read', 'write', 'delete'],
user: ['read'],
moderator: ['read', 'write']
};
Exclude<T, U>
Creates a type by excluding from T all properties that are assignable to U.
type AllowedColors = 'red' | 'green' | 'blue' | 'yellow' | 'purple';
type PrimaryColors = Exclude<AllowedColors, 'yellow' | 'purple'>;
// 'red' | 'green' | 'blue'
type NonFunctionPropertyNames<T> = {
[K in keyof T]: T[K] extends Function ? never : K;
}[keyof T];
type NonFunctionProperties<T> = Pick<T, NonFunctionPropertyNames<T>>;
class Example {
property1: string = '';
property2: number = 0;
method1(): void {}
method2(): string { return ''; }
}
type ExampleProperties = NonFunctionProperties<Example>;
// { property1: string; property2: number; }
Extract<T, U>
Creates a type by extracting from T all properties that are assignable to U.
type StringOrNumber = string | number | boolean;
type StringsOnly = Extract<StringOrNumber, string>;
// string
type FunctionPropertyNames<T> = {
[K in keyof T]: T[K] extends Function ? K : never;
}[keyof T];
type FunctionProperties<T> = Pick<T, FunctionPropertyNames<T>>;
type ExampleMethods = FunctionProperties<Example>;
// { method1: () => void; method2: () => string; }
NonNullable<T>
Creates a type by excluding null and undefined from T.
type NullableString = string | null | undefined;
type String = NonNullable<NullableString>;
// string
function processValue(value: string | null | undefined): NonNullable<typeof value> | null {
if (value === null || value === undefined) {
return null;
}
return value.toUpperCase(); // TypeScript knows value is string here
}
Function-Related Utility Types
Parameters<T>
Extracts the parameter types of a function type T.
function createUser(name: string, age: number, email: string): User {
return {} as User;
}
type CreateUserParams = Parameters<typeof createUser>;
// [string, number, string]
function callWithSameParams(fn: typeof createUser, params: CreateUserParams): User {
return fn(...params);
}
// Generic wrapper function
function logAndCall<T extends (...args: any[]) => any>(
fn: T,
...args: Parameters<T>
): ReturnType<T> {
console.log('Calling function with:', args);
return fn(...args);
}
ReturnType<T>
Extracts the return type of a function type T.
function getUser(id: number): { id: number; name: string } {
return { id, name: 'John' };
}
type UserType = ReturnType<typeof getUser>;
// { id: number; name: string }
function processUserData(user: UserType): string {
return `User ${user.id}: ${user.name}`;
}
// Async function return type
async function fetchData(): Promise<{ data: string[]; count: number }> {
return { data: [], count: 0 };
}
type FetchDataResult = ReturnType<typeof fetchData>;
// Promise<{ data: string[]; count: number }>
type UnwrappedFetchData = Awaited<ReturnType<typeof fetchData>>;
// { data: string[]; count: number }
ConstructorParameters<T>
Extracts the parameter types of a constructor function type T.
class Database {
constructor(
private host: string,
private port: number,
private options?: { ssl: boolean }
) {}
}
type DatabaseParams = ConstructorParameters<typeof Database>;
// [string, number, { ssl: boolean }?]
function createDatabase(...params: DatabaseParams): Database {
return new Database(...params);
}
InstanceType<T>
Extracts the instance type of a constructor function type T.
type DatabaseInstance = InstanceType<typeof Database>;
// Database
function processDatabase(db: DatabaseInstance): void {
// db is of type Database
}
Template Literal Types
TypeScript utility types work well with template literal types for advanced string manipulation.
type EventNames = 'click' | 'hover' | 'focus';
type EventHandlers = `on${Capitalize<EventNames>}`;
// 'onClick' | 'onHover' | 'onFocus'
type ApiEndpoints = '/users' | '/posts' | '/comments';
type ApiMethods = 'GET' | 'POST' | 'PUT' | 'DELETE';
type ApiRoutes = `${ApiMethods} ${ApiEndpoints}`;
// 'GET /users' | 'POST /users' | 'PUT /users' | ... etc
// Recursive template literal utility
type Join<T extends readonly string[], D extends string> = T extends readonly [infer F, ...infer R]
? F extends string
? R extends readonly string[]
? R['length'] extends 0
? F
: `${F}${D}${Join<R, D>}`
: never
: never
: '';
type Path = Join<['users', 'profile', 'settings'], '/'>;
// 'users/profile/settings'
Custom Utility Types
You can create your own utility types for specific use cases.
// Deep partial - makes all nested properties optional
type DeepPartial<T> = {
[P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P];
};
interface NestedConfig {
api: {
url: string;
timeout: number;
retries: {
count: number;
delay: number;
};
};
ui: {
theme: string;
language: string;
};
}
type PartialNestedConfig = DeepPartial<NestedConfig>;
// All properties at all levels are optional
// Mutable - removes readonly from all properties
type Mutable<T> = {
-readonly [P in keyof T]: T[P];
};
type MutablePoint = Mutable<ReadonlyPoint>;
// { x: number; y: number } (without readonly)
// Optional by keys
type OptionalByKeys<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;
type UserWithOptionalEmail = OptionalByKeys<User, 'email'>;
// { id: number; name: string; email?: string; password: string; ... }
// Required by keys
type RequiredByKeys<T, K extends keyof T> = Omit<T, K> & Required<Pick<T, K>>;
// Nullable
type Nullable<T> = {
[P in keyof T]: T[P] | null;
};
// Not null
type NotNull<T> = {
[P in keyof T]: NonNullable<T[P]>;
};
Practical Examples
Combining utility types for real-world scenarios.
// API response wrapper
interface ApiResponse<T> {
data: T;
status: number;
message: string;
}
type UserApiResponse = ApiResponse<PublicUser>;
// Form state management
type FormState<T> = {
values: Partial<T>;
errors: Partial<Record<keyof T, string>>;
touched: Partial<Record<keyof T, boolean>>;
isSubmitting: boolean;
};
type UserFormState = FormState<UserInput>;
// Database entity with metadata
type Entity<T> = T & {
id: string;
createdAt: Date;
updatedAt: Date;
version: number;
};
type UserEntity = Entity<Omit<User, 'id'>>;
// Event system
type EventMap = {
userCreated: { user: User };
userUpdated: { user: User; changes: Partial<User> };
userDeleted: { userId: number };
};
type EventHandler<T extends keyof EventMap> = (data: EventMap[T]) => void;
function addEventListener<T extends keyof EventMap>(
event: T,
handler: EventHandler<T>
): void {
// Implementation
}
// Usage
addEventListener('userCreated', (data) => {
console.log('User created:', data.user.name); // Fully typed
});
TypeScript utility types provide powerful tools for type transformation and manipulation. They help create more maintainable and type-safe code by reducing duplication and providing elegant solutions for common type manipulation scenarios.