typescript1/20/20247 min read
TypeScript Utility Types
Master TypeScript's built-in utility types for advanced type manipulation
typescriptutility-typestype-manipulationgenerics
By Zen Frontend
TypeScript Utility Types
TypeScript provides a set of built-in utility types that help you transform and manipulate existing types. These utilities are essential for creating flexible and reusable type definitions.
Built-in Utility Types
Partial
Makes all properties of 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;
// }
// Usage
function updateUser(id: number, updates: Partial<User>) {
// Only some properties need to be provided
return { id, ...updates };
}
updateUser(1, { name: 'John' }); // OK
updateUser(1, { name: 'John', age: 30 }); // OK
Required
Makes all properties of T required.
interface Config {
apiUrl?: string;
timeout?: number;
retries?: number;
}
type RequiredConfig = Required<Config>;
// Equivalent to:
// {
// apiUrl: string;
// timeout: number;
// retries: number;
// }
// Usage
function createClient(config: RequiredConfig) {
// All properties are guaranteed to be present
return {
url: config.apiUrl,
timeout: config.timeout,
retries: config.retries
};
}
Readonly
Makes all properties of T readonly.
interface MutableUser {
id: number;
name: string;
email: string;
}
type ReadonlyUser = Readonly<MutableUser>;
// Equivalent to:
// {
// readonly id: number;
// readonly name: string;
// readonly email: string;
// }
// Usage
const user: ReadonlyUser = { id: 1, name: 'John', email: 'john@example.com' };
// user.name = 'Jane'; // Error: Cannot assign to 'name' because it is a read-only property
Pick<T, K>
Selects specific properties from T.
interface User {
id: number;
name: string;
email: string;
password: string;
createdAt: Date;
updatedAt: Date;
}
type UserPublicInfo = Pick<User, 'id' | 'name' | 'email'>;
// Equivalent to:
// {
// id: number;
// name: string;
// email: string;
// }
// Usage
function getUserPublicInfo(user: User): UserPublicInfo {
return {
id: user.id,
name: user.name,
email: user.email
};
}
Omit<T, K>
Excludes specific properties from T.
interface User {
id: number;
name: string;
email: string;
password: string;
createdAt: Date;
updatedAt: Date;
}
type UserWithoutPassword = Omit<User, 'password'>;
// Equivalent to:
// {
// id: number;
// name: string;
// email: string;
// createdAt: Date;
// updatedAt: Date;
// }
// Usage
function createUser(userData: Omit<User, 'id' | 'createdAt' | 'updatedAt'>) {
return {
...userData,
id: Math.random(),
createdAt: new Date(),
updatedAt: new Date()
};
}
Record<K, V>
Creates an object type with keys of type K and values of type V.
type Status = 'pending' | 'approved' | 'rejected';
type StatusMessages = Record<Status, string>;
// Equivalent to:
// {
// pending: string;
// approved: string;
// rejected: string;
// }
const messages: StatusMessages = {
pending: 'Your request is being processed',
approved: 'Your request has been approved',
rejected: 'Your request has been rejected'
};
// Usage with string keys
type UserRoles = Record<string, string[]>;
const userRoles: UserRoles = {
admin: ['read', 'write', 'delete'],
user: ['read'],
guest: []
};
Exclude<T, U>
Excludes types from T that are assignable to U.
type AllColors = 'red' | 'green' | 'blue' | 'yellow' | 'purple';
type PrimaryColors = 'red' | 'green' | 'blue';
type SecondaryColors = Exclude<AllColors, PrimaryColors>;
// Equivalent to: 'yellow' | 'purple'
// Usage
function paintWithSecondary(color: SecondaryColors) {
// Only 'yellow' or 'purple' are allowed
console.log(`Painting with ${color}`);
}
Extract<T, U>
Extracts types from T that are assignable to U.
type AllColors = 'red' | 'green' | 'blue' | 'yellow' | 'purple';
type WarmColors = 'red' | 'yellow' | 'orange';
type AvailableWarmColors = Extract<AllColors, WarmColors>;
// Equivalent to: 'red' | 'yellow'
// Usage
function paintWithWarm(color: AvailableWarmColors) {
// Only 'red' or 'yellow' are allowed
console.log(`Painting with ${color}`);
}
NonNullable
Excludes null and undefined from T.
type MaybeString = string | null | undefined;
type DefiniteString = NonNullable<MaybeString>;
// Equivalent to: string
// Usage
function processString(value: MaybeString): DefiniteString {
if (value == null) {
throw new Error('Value cannot be null or undefined');
}
return value; // Type is now string
}
Parameters
Extracts the parameter types of a function type T.
function createUser(name: string, age: number, email: string) {
return { name, age, email };
}
type CreateUserParams = Parameters<typeof createUser>;
// Equivalent to: [string, number, string]
// Usage
function callWithDefaults(fn: typeof createUser, ...args: CreateUserParams) {
return fn(...args);
}
ReturnType
Extracts the return type of a function type T.
function getUser() {
return {
id: 1,
name: 'John',
email: 'john@example.com'
};
}
type User = ReturnType<typeof getUser>;
// Equivalent to:
// {
// id: number;
// name: string;
// email: string;
// }
// Usage
function processUser(user: User) {
console.log(`Processing user: ${user.name}`);
}
Advanced Utility Types
Custom Utility Types
// Make specific properties optional
type PartialBy<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;
interface User {
id: number;
name: string;
email: string;
password: string;
}
type UserRegistration = PartialBy<User, 'id'>;
// Equivalent to:
// {
// name: string;
// email: string;
// password: string;
// id?: number;
// }
// Make specific properties required
type RequiredBy<T, K extends keyof T> = Omit<T, K> & Required<Pick<T, K>>;
type UserWithRequiredEmail = RequiredBy<User, 'email'>;
// email is now required (though it already was)
// Deep partial
type DeepPartial<T> = {
[P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P];
};
interface NestedUser {
id: number;
profile: {
name: string;
avatar: {
url: string;
alt: string;
};
};
}
type PartialNestedUser = DeepPartial<NestedUser>;
// All nested properties are now optional
Conditional Utility Types
// Extract function return type, handling async functions
type Awaited<T> = T extends Promise<infer U> ? U : T;
type SyncResult = Awaited<string>; // string
type AsyncResult = Awaited<Promise<number>>; // number
// Check if a type is a function
type IsFunction<T> = T extends (...args: any[]) => any ? true : false;
type Test1 = IsFunction<string>; // false
type Test2 = IsFunction<() => void>; // true
// Get the first parameter type
type FirstParameter<T> = T extends (first: infer P, ...args: any[]) => any ? P : never;
type FirstParam = FirstParameter<(name: string, age: number) => void>; // string
Practical Examples
API Response Types
interface ApiResponse<T> {
data: T;
status: number;
message: string;
timestamp: string;
}
type UserApiResponse = ApiResponse<User>;
type PartialUserApiResponse = ApiResponse<Partial<User>>;
// Extract just the data type
type ApiData<T> = T extends ApiResponse<infer U> ? U : never;
type UserData = ApiData<UserApiResponse>; // User
Form Handling
interface UserForm {
name: string;
email: string;
password: string;
confirmPassword: string;
}
// Form data without password confirmation
type UserFormData = Omit<UserForm, 'confirmPassword'>;
// Form validation errors
type FormErrors<T> = {
[K in keyof T]?: string;
};
type UserFormErrors = FormErrors<UserForm>;
// Equivalent to:
// {
// name?: string;
// email?: string;
// password?: string;
// confirmPassword?: string;
// }
State Management
interface AppState {
user: User | null;
loading: boolean;
error: string | null;
theme: 'light' | 'dark';
}
// Actions that can be dispatched
type AppAction =
| { type: 'SET_USER'; payload: User }
| { type: 'SET_LOADING'; payload: boolean }
| { type: 'SET_ERROR'; payload: string | null }
| { type: 'TOGGLE_THEME' };
// Reducer function type
type AppReducer = (state: AppState, action: AppAction) => AppState;
// Selector function type
type Selector<T> = (state: AppState) => T;
type UserSelector = Selector<User | null>;
Key Concepts
- Type Transformation: Utility types transform existing types
- Composability: Utility types can be combined and nested
- Generic Constraints: Using
extends
to constrain type parameters - Conditional Types: Using
extends
andinfer
for type logic - Template Literal Types: For string manipulation (TypeScript 4.1+)
Common Interview Questions
- What's the difference between
Pick
andOmit
? - How would you create a deep partial type?
- What's the purpose of
Record<K, V>
? - How do you extract function parameter types?
- What's the difference between
Exclude
andExtract
?
Related Topics
- Type vs Interface
- Generics
- Conditional Types
- Template Literal Types
- Mapped Types