TypeScript provides a treasure trove of built-in utility types that save you from writing boilerplate type transformations. Think of them as your type-level standard library - pre-built tools for common type manipulations. Let’s explore every utility type and learn how to create our own.
Object Manipulation Utilities
Partial<T>
Makes all properties optional:
interface User {
id: string;
name: string;
email: string;
age: number;
}
type PartialUser = Partial<User>;
// {
// id?: string;
// name?: string;
// email?: string;
// age?: number;
// }
// Perfect for update operations
function updateUser(id: string, updates: Partial<User>) {
// Only update provided fields
return database.update(id, updates);
}
updateUser('123', { name: 'Alice' }); // ✅ Only updating name
updateUser('123', {}); // ✅ No updatesRequired<T>
Makes all properties required:
interface Config {
apiUrl?: string;
timeout?: number;
retries?: number;
}
type RequiredConfig = Required<Config>;
// {
// apiUrl: string;
// timeout: number;
// retries: number;
// }
// Ensure all config is provided
function initialize(config: Required<Config>) {
// All properties guaranteed to exist
connect(config.apiUrl, config.timeout);
}Readonly<T>
Makes all properties readonly:
interface State {
count: number;
user: User;
}
type ReadonlyState = Readonly<State>;
// {
// readonly count: number;
// readonly user: User;
// }
// Prevent accidental mutations
function processState(state: Readonly<State>) {
// state.count = 5; // ❌ Error: Cannot assign to 'count'
return { ...state, count: state.count + 1 }; // ✅ Create new object
}Record<K, T>
Creates an object type with keys K and values T:
type Role = 'admin' | 'user' | 'guest';
type Permissions = Record<Role, string[]>;
// {
// admin: string[];
// user: string[];
// guest: string[];
// }
const permissions: Permissions = {
admin: ['read', 'write', 'delete'],
user: ['read', 'write'],
guest: ['read'],
};
// Dynamic keys
type Cache<T> = Record<string, T>;
const userCache: Cache<User> = {
'user-1': { id: '1', name: 'Alice' },
'user-2': { id: '2', name: 'Bob' },
};Property Selection Utilities
Pick<T, K>
Picks specific properties from a type:
interface User {
id: string;
name: string;
email: string;
password: string;
createdAt: Date;
}
type UserPreview = Pick<User, 'id' | 'name'>;
// { id: string; name: string; }
type PublicUser = Pick<User, 'id' | 'name' | 'email'>;
// { id: string; name: string; email: string; }
// Use in function parameters
function displayUser(user: Pick<User, 'name' | 'email'>) {
return `${user.name} (${user.email})`;
}Omit<T, K>
Omits specific properties from a type:
type UserWithoutPassword = Omit<User, 'password'>;
// { id: string; name: string; email: string; createdAt: Date; }
type UserUpdate = Omit<User, 'id' | 'createdAt'>;
// { name: string; email: string; password: string; }
// Remove sensitive data
function sanitizeUser(user: User): Omit<User, 'password'> {
const { password, ...sanitized } = user;
return sanitized;
}Union Manipulation Utilities
Exclude<T, U>
Excludes types from a union:
type Status = 'idle' | 'loading' | 'success' | 'error';
type ActiveStatus = Exclude<Status, 'idle'>;
// 'loading' | 'success' | 'error'
type Primitive = string | number | boolean | null | undefined;
type NonNullablePrimitive = Exclude<Primitive, null | undefined>;
// string | number | boolean
// Filter union members
type Events = 'click' | 'focus' | 'blur' | 'change' | 'submit';
type NonFormEvents = Exclude<Events, 'change' | 'submit'>;
// 'click' | 'focus' | 'blur'Extract<T, U>
Extracts types from a union:
type Status = 'idle' | 'loading' | 'success' | 'error';
type DoneStatus = Extract<Status, 'success' | 'error'>;
// 'success' | 'error'
// Extract specific shapes from union
type Shape =
| { kind: 'circle'; radius: number }
| { kind: 'square'; size: number }
| { kind: 'triangle'; base: number; height: number };
type RoundShape = Extract<Shape, { kind: 'circle' }>;
// { kind: 'circle'; radius: number }
// Extract functions from union
type Mixed = string | number | (() => void) | (() => string);
type Functions = Extract<Mixed, Function>;
// (() => void) | (() => string)NonNullable<T>
Removes null and undefined:
type MaybeString = string | null | undefined;
type DefiniteString = NonNullable<MaybeString>;
// string
type MaybeUser = User | null | undefined;
type DefiniteUser = NonNullable<MaybeUser>;
// User
// Clean up optional values
function processValue<T>(value: T | null | undefined): NonNullable<T> {
if (value === null || value === undefined) {
throw new Error('Value is required');
}
return value; // Type is NonNullable<T>
}Function Utilities
Parameters<T>
Extracts function parameters:
function createUser(name: string, age: number, email?: string): User {
return { id: '1', name, age, email: email || '' };
}
type CreateUserParams = Parameters<typeof createUser>;
// [name: string, age: number, email?: string]
// Use with rest parameters
function log(...args: string[]): void {
console.log(...args);
}
type LogParams = Parameters<typeof log>;
// string[]
// Apply to other functions
function applyFunction<F extends (...args: any[]) => any>(
fn: F,
args: Parameters<F>,
): ReturnType<F> {
return fn(...args);
}ReturnType<T>
Extracts function return type:
function getUser(): User {
return { id: '1', name: 'Alice' };
}
type UserReturn = ReturnType<typeof getUser>;
// User
// With async functions
async function fetchData(): Promise<string[]> {
return ['a', 'b', 'c'];
}
type DataReturn = ReturnType<typeof fetchData>;
// Promise<string[]>
// Generic return type
function identity<T>(value: T): T {
return value;
}
type StringIdentity = ReturnType<typeof identity<string>>;
// stringConstructorParameters<T>
Extracts constructor parameters:
class User {
constructor(
public name: string,
public age: number,
private id: string,
) {}
}
type UserConstructorParams = ConstructorParameters<typeof User>;
// [name: string, age: number, id: string]
// Use with factory functions
function createInstance<T extends new (...args: any[]) => any>(
Constructor: T,
...args: ConstructorParameters<T>
): InstanceType<T> {
return new Constructor(...args);
}InstanceType<T>
Gets instance type of a constructor:
type UserInstance = InstanceType<typeof User>;
// User
// With built-in constructors
type DateInstance = InstanceType<typeof Date>;
// Date
type ErrorInstance = InstanceType<typeof Error>;
// Error
// Generic factory
function factory<T extends new (...args: any[]) => any>(Constructor: T): InstanceType<T> {
return new Constructor();
}String Manipulation Utilities
Uppercase<T>
type Shout = Uppercase<'hello'>;
// 'HELLO'
type Methods = 'get' | 'post' | 'put' | 'delete';
type UpperMethods = Uppercase<Methods>;
// 'GET' | 'POST' | 'PUT' | 'DELETE'Lowercase<T>
type Whisper = Lowercase<'HELLO'>;
// 'hello'
type HTTPMethods = 'GET' | 'POST' | 'PUT' | 'DELETE';
type LowerMethods = Lowercase<HTTPMethods>;
// 'get' | 'post' | 'put' | 'delete'Capitalize<T>
type Title = Capitalize<'hello world'>;
// 'Hello world'
type EventNames = 'click' | 'focus' | 'blur';
type HandlerNames = `on${Capitalize<EventNames>}`;
// 'onClick' | 'onFocus' | 'onBlur'Uncapitalize<T>
type Normal = Uncapitalize<'Hello World'>;
// 'hello World'
type PropertyNames = 'Name' | 'Age' | 'Email';
type LowerPropertyNames = Uncapitalize<PropertyNames>;
// 'name' | 'age' | 'email'Promise Utilities
Awaited<T>
Unwraps Promise types:
type PromiseString = Promise<string>;
type StringType = Awaited<PromiseString>;
// string
// Nested promises
type NestedPromise = Promise<Promise<Promise<number>>>;
type NumberType = Awaited<NestedPromise>;
// number
// With async functions
async function fetchUser(): Promise<User> {
return { id: '1', name: 'Alice' };
}
type FetchedUser = Awaited<ReturnType<typeof fetchUser>>;
// UserReact-Specific Utility Patterns
Component Props Utilities
// Extract props from any component
type PropsOf<C> = C extends React.ComponentType<infer P> ? P : never;
// Make specific props optional
type Optional<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;
interface ButtonProps {
label: string;
onClick: () => void;
disabled: boolean;
}
type FlexibleButton = Optional<ButtonProps, 'disabled' | 'onClick'>;
// { label: string; disabled?: boolean; onClick?: () => void; }Event Handler Types
// Extract event type from handler
type EventOf<T> = T extends (event: infer E) => void ? E : never;
type ClickHandler = (e: React.MouseEvent) => void;
type ClickEvent = EventOf<ClickHandler>;
// React.MouseEvent
// Create handler type
type Handler<E> = (event: E) => void;
type MouseHandler = Handler<React.MouseEvent>;Custom Utility Types
DeepPartial
type DeepPartial<T> = {
[P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P];
};
interface Config {
server: {
host: string;
port: number;
ssl: {
enabled: boolean;
cert: string;
};
};
}
type PartialConfig = DeepPartial<Config>;
// Everything is optional, including nested propertiesDeepReadonly
type DeepReadonly<T> = {
readonly [P in keyof T]: T[P] extends object ? DeepReadonly<T[P]> : T[P];
};
type ImmutableConfig = DeepReadonly<Config>;
// Everything is readonly, including nested propertiesMutable
type Mutable<T> = {
-readonly [P in keyof T]: T[P];
};
interface ReadonlyUser {
readonly id: string;
readonly name: string;
}
type MutableUser = Mutable<ReadonlyUser>;
// { id: string; name: string; }PickByValue
type PickByValue<T, V> = {
[K in keyof T as T[K] extends V ? K : never]: T[K];
};
interface Mixed {
id: number;
name: string;
isActive: boolean;
description: string;
count: number;
}
type StringFields = PickByValue<Mixed, string>;
// { name: string; description: string; }
type NumberFields = PickByValue<Mixed, number>;
// { id: number; count: number; }OmitByValue
type OmitByValue<T, V> = {
[K in keyof T as T[K] extends V ? never : K]: T[K];
};
type NonStringFields = OmitByValue<Mixed, string>;
// { id: number; isActive: boolean; count: number; }Nullable
type Nullable<T> = T | null;
type Optional<T> = T | undefined;
type Maybe<T> = T | null | undefined;
type NullableString = Nullable<string>; // string | null
type OptionalNumber = Optional<number>; // number | undefined
type MaybeUser = Maybe<User>; // User | null | undefinedValueOf
type ValueOf<T> = T[keyof T];
interface Colors {
red: '#ff0000';
green: '#00ff00';
blue: '#0000ff';
}
type ColorValue = ValueOf<Colors>;
// '#ff0000' | '#00ff00' | '#0000ff'Advanced Combinations
API Response Types
// Combine multiple utilities
type ApiResponse<T> = Readonly<{
data: T;
status: number;
timestamp: Date;
}>;
type PartialApiResponse<T> = Partial<ApiResponse<T>>;
type ApiResponseData<T> = Pick<ApiResponse<T>, 'data'>;
type ApiMetadata<T> = Omit<ApiResponse<T>, 'data'>;
// Create variations
type UserResponse = ApiResponse<User>;
type PartialUserResponse = PartialApiResponse<User>;
type UserData = ApiResponseData<User>;
type UserMetadata = ApiMetadata<User>;Form State Management
// Form field state
type FieldState<T> = {
value: T;
error?: string;
touched: boolean;
dirty: boolean;
};
// Convert model to form
type FormState<T> = {
[K in keyof T]: FieldState<T[K]>;
};
// Form with validation
type ValidatedForm<T> = FormState<T> & {
isValid: boolean;
isSubmitting: boolean;
errors: Partial<Record<keyof T, string>>;
};
interface LoginData {
email: string;
password: string;
}
type LoginForm = ValidatedForm<LoginData>;React HOC Types
// HOC that adds loading state
type WithLoading<P> = P & {
loading?: boolean;
error?: Error;
};
// HOC that injects props
type InjectProps<P, I> = Omit<P, keyof I> & Partial<I>;
// Theme injection
interface ThemeProps {
theme: {
colors: Record<string, string>;
fonts: Record<string, string>;
};
}
type WithTheme<P> = InjectProps<P, ThemeProps>;
// Usage
interface ButtonProps {
label: string;
onClick: () => void;
theme?: ThemeProps['theme'];
}
type ThemedButton = WithTheme<ButtonProps>;
// { label: string; onClick: () => void; theme?: ThemeProps['theme']; }Type Guards with Utilities
// Create type guards for utility types
function isPartial<T>(value: T | Partial<T>): value is Partial<T> {
return Object.values(value).some((v) => v === undefined);
}
function isRequired<T>(value: Partial<T>): value is Required<T> {
return Object.values(value).every((v) => v !== undefined);
}
function isNonNullable<T>(value: T): value is NonNullable<T> {
return value !== null && value !== undefined;
}
// Usage
function processUser(user: Partial<User> | User) {
if (isRequired(user)) {
// user is Required<User> (all fields present)
console.log(user.email); // Safe access
}
}Performance Considerations
Avoid Deep Nesting
// ❌ Can be slow
type DeepNested<T> = {
[P in keyof T]: DeepNested<DeepNested<DeepNested<T[P]>>>;
};
// ✅ Limit depth
type LimitedDepth<T, D extends number = 3> = D extends 0
? T
: { [P in keyof T]: T[P] extends object ? LimitedDepth<T[P], Prev[D]> : T[P] };
type Prev = [never, 0, 1, 2, 3];Use Type Aliases
// ✅ Good: Reusable and clear
type UserUpdate = Partial<Omit<User, 'id'>>;
type PublicUser = Pick<User, 'id' | 'name' | 'email'>;
// ❌ Bad: Inline everywhere
function update(data: Partial<Omit<User, 'id'>>) {}
function display(user: Pick<User, 'id' | 'name' | 'email'>) {}Best Practices
Combine for Clarity
// Instead of complex inline types
type UserFormData = Partial<Pick<User, 'name' | 'email' | 'age'>>;
// Is clearer than
type UserFormData2 = {
name?: string;
email?: string;
age?: number;
};Create Domain-Specific Utilities
// Project-specific utilities
type Loadable<T> = {
data?: T;
loading: boolean;
error?: Error;
};
type Timestamped<T> = T & {
createdAt: Date;
updatedAt: Date;
};
type Identifiable<T> = T & {
id: string;
};Document Complex Utilities
/**
* Makes specified keys optional while keeping others required
* @example
* type Result = PartialBy<User, 'email' | 'age'>
* // { id: string; name: string; email?: string; age?: number; }
*/
type PartialBy<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;Summary
Utility types are essential tools in your TypeScript toolbox:
- Object utilities (
Partial,Required,Readonly,Record) - Transform object types - Selection utilities (
Pick,Omit) - Select or exclude properties - Union utilities (
Exclude,Extract,NonNullable) - Manipulate union types - Function utilities (
Parameters,ReturnType) - Extract function type information - String utilities (
Uppercase,Lowercase, etc.) - Transform string literal types - Custom utilities - Build your own for domain-specific needs
Master these utilities, and you’ll write cleaner, more maintainable TypeScript code with less repetition!