Utility Types#
TypeScript provides powerful built-in utility types that help you transform and manipulate types. These are essential for writing clean, DRY (Don't Repeat Yourself) code.
Essential Utility Types#
Partial<T>#
Makes all properties of T optional.
interface User {
id: number;
name: string;
email: string;
age: number;
}
type PartialUser = Partial<User>;
// { id?: number; name?: string; email?: string; age?: number; }
// Useful for update functions
function updateUser(id: number, updates: Partial<User>): User {
const user = getUserById(id);
return { ...user, ...updates };
}
updateUser(1, { name: "Alice" }); // ✅ OK
updateUser(1, { email: "alice@example.com" }); // ✅ OK
updateUser(1, { name: "Alice", age: 26 }); // ✅ OK
Required<T>#
Makes all properties of T required.
interface Config {
apiUrl?: string;
timeout?: number;
retries?: number;
}
type RequiredConfig = Required<Config>;
// { apiUrl: string; timeout: number; retries: number; }
function initializeApp(config: RequiredConfig): void {
// All properties are guaranteed to exist
console.log(config.apiUrl); // No need for optional chaining
console.log(config.timeout);
console.log(config.retries);
}
Readonly<T>#
Makes all properties of T readonly.
interface Settings {
theme: string;
language: string;
}
type ReadonlySettings = Readonly<Settings>;
// { readonly theme: string; readonly language: string; }
const settings: ReadonlySettings = {
theme: "dark",
language: "en"
};
// settings.theme = "light"; // ❌ Error: Cannot assign to 'theme'
// Useful for immutable state
function createImmutableState<T>(initial: T): Readonly<T> {
return Object.freeze(initial);
}
Pick<T, K>#
Creates a type by picking specific properties from T.
interface User {
id: number;
name: string;
email: string;
password: string;
createdAt: Date;
updatedAt: Date;
}
type UserPreview = Pick<User, "id" | "name">;
// { id: number; name: string; }
type UserCredentials = Pick<User, "email" | "password">;
// { email: string; password: string; }
// Useful for API responses
function getUserPreview(id: number): UserPreview {
const user = getUser(id);
return {
id: user.id,
name: user.name
};
}
Omit<T, K>#
Creates a type by omitting specific properties from T.
interface User {
id: number;
name: string;
email: string;
password: string;
}
type UserWithoutPassword = Omit<User, "password">;
// { id: number; name: string; email: string; }
type PublicUser = Omit<User, "password" | "email">;
// { id: number; name: string; }
// Useful for public API responses
function getPublicProfile(id: number): PublicUser {
const user = getUser(id);
return {
id: user.id,
name: user.name
};
}
Record<K, T>#
Creates an object type with keys of type K and values of type 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"]
};
// Creating a dictionary/map
type UserMap = Record<number, User>;
const users: UserMap = {
1: { id: 1, name: "Alice", email: "alice@example.com" },
2: { id: 2, name: "Bob", email: "bob@example.com" }
};
// Error messages
type ErrorMessages = Record<string, string>;
const errors: ErrorMessages = {
required: "This field is required",
email: "Please enter a valid email",
minLength: "Must be at least 8 characters"
};
Exclude<T, U>#
Excludes types from T that are assignable to U.
type AllStatus = "pending" | "approved" | "rejected" | "cancelled";
type ActiveStatus = Exclude<AllStatus, "cancelled">;
// "pending" | "approved" | "rejected"
type Primitive = string | number | boolean | null | undefined;
type NonNullPrimitive = Exclude<Primitive, null | undefined>;
// string | number | boolean
Extract<T, U>#
Extracts types from T that are assignable to U.
type AllStatus = "pending" | "approved" | "rejected" | "cancelled";
type CompletedStatus = Extract<AllStatus, "approved" | "rejected">;
// "approved" | "rejected"
type T1 = Extract<string | number | (() => void), Function>;
// () => void
NonNullable<T>#
Removes null and undefined from T.
type MaybeString = string | null | undefined;
type DefiniteString = NonNullable<MaybeString>;
// string
type User = {
id: number;
name: string;
email: string | null;
};
type Email = NonNullable<User["email"]>;
// string
Function Utility Types#
ReturnType<T>#
Extracts the return type of a function.
function getUser() {
return {
id: 1,
name: "Alice",
email: "alice@example.com"
};
}
type User = ReturnType<typeof getUser>;
// { id: number; name: string; email: string; }
// With async functions
async function fetchData() {
return { data: [1, 2, 3], status: 200 };
}
type FetchResult = ReturnType<typeof fetchData>;
// Promise<{ data: number[]; status: number; }>
// Unwrap promise
type UnwrappedResult = Awaited<ReturnType<typeof fetchData>>;
// { data: number[]; status: number; }
Parameters<T>#
Extracts parameter types of a function as a tuple.
function createUser(name: string, age: number, email: string) {
return { name, age, email };
}
type CreateUserParams = Parameters<typeof createUser>;
// [name: string, age: number, email: string]
// Using with apply
function wrapper(...args: Parameters<typeof createUser>) {
console.log("Creating user with:", args);
return createUser(...args);
}
ConstructorParameters<T>#
Extracts constructor parameter types.
class User {
constructor(public name: string, public age: number) {}
}
type UserConstructorParams = ConstructorParameters<typeof User>;
// [name: string, age: number]
function createInstance(...args: ConstructorParameters<typeof User>) {
return new User(...args);
}
InstanceType<T>#
Gets the instance type of a constructor function.
class User {
constructor(public name: string) {}
greet() {
return `Hello, ${this.name}`;
}
}
type UserInstance = InstanceType<typeof User>;
// User
function processUser(user: UserInstance) {
console.log(user.greet());
}
Advanced Utility Types#
Awaited<T>#
Recursively unwraps Promise types.
type A = Awaited<Promise<string>>;
// string
type B = Awaited<Promise<Promise<number>>>;
// number
// Useful with async functions
async function fetchUser() {
return { id: 1, name: "Alice" };
}
type User = Awaited<ReturnType<typeof fetchUser>>;
// { id: number; name: string; }
Uppercase<T>, Lowercase<T>, Capitalize<T>, Uncapitalize<T>#
String manipulation utility types.
type Greeting = "hello world";
type Loud = Uppercase<Greeting>;
// "HELLO WORLD"
type Quiet = Lowercase<Greeting>;
// "hello world"
type Capitalized = Capitalize<Greeting>;
// "Hello world"
type Uncapitalized = Uncapitalize<"Hello World">;
// "hello World"
// Practical use with template literals
type EventName = "click" | "focus" | "blur";
type EventHandler = `on${Capitalize<EventName>}`;
// "onClick" | "onFocus" | "onBlur"
Practical Examples#
API Response Wrapper#
interface ApiResponse<T> {
data: T;
error: string | null;
status: number;
}
// Utility to extract data type
type UnwrapApiResponse<T> = T extends ApiResponse<infer U> ? U : never;
type UserResponse = ApiResponse<User>;
type User = UnwrapApiResponse<UserResponse>;
// Success/Error types
type SuccessResponse<T> = Required<Pick<ApiResponse<T>, "data" | "status">> &
Pick<ApiResponse<T>, "error">;
type ErrorResponse = Required<Pick<ApiResponse<never>, "error" | "status">> &
Omit<ApiResponse<never>, "data">;
Form Field Utilities#
interface FormField<T = string> {
value: T;
error: string | null;
touched: boolean;
dirty: boolean;
}
// Convert form fields to just values
type FormValues<T> = {
[K in keyof T]: T[K] extends FormField<infer U> ? U : never;
};
interface LoginForm {
email: FormField<string>;
password: FormField<string>;
rememberMe: FormField<boolean>;
}
type LoginValues = FormValues<LoginForm>;
// { email: string; password: string; rememberMe: boolean; }
// Extract errors
type FormErrors<T> = Partial<Record<keyof T, string>>;
const errors: FormErrors<LoginForm> = {
email: "Invalid email",
password: "Password too short"
};
State Management#
interface State {
user: User | null;
posts: Post[];
loading: boolean;
error: string | null;
}
// Make entire state readonly
type ReadonlyState = Readonly<State>;
// Make specific parts updatable
type UpdatableState = Pick<State, "user" | "posts">;
// Create action types
type StateKeys = keyof State;
type SetAction<K extends StateKeys> = {
type: `SET_${Uppercase<K>}`;
payload: State[K];
};
type SetUserAction = SetAction<"user">;
// { type: "SET_USER"; payload: User | null; }
Typed Event Handlers#
interface DOMEvents {
click: MouseEvent;
keydown: KeyboardEvent;
submit: SubmitEvent;
change: Event;
}
type EventHandler<K extends keyof DOMEvents> = (event: DOMEvents[K]) => void;
const handleClick: EventHandler<"click"> = (event) => {
console.log(event.clientX, event.clientY); // MouseEvent properties
};
const handleKeydown: EventHandler<"keydown"> = (event) => {
console.log(event.key); // KeyboardEvent properties
};
// Generic event listener
function addEventListener<K extends keyof DOMEvents>(
element: HTMLElement,
eventType: K,
handler: EventHandler<K>
): void {
element.addEventListener(eventType, handler as EventListener);
}
Database Query Builder#
interface User {
id: number;
name: string;
email: string;
age: number;
createdAt: Date;
}
// Select specific fields
type SelectFields<T, K extends keyof T> = Pick<T, K>;
type UserPreview = SelectFields<User, "id" | "name">;
// { id: number; name: string; }
// Update payload (all optional except id)
type UpdatePayload<T> = Partial<Omit<T, "id">> & Pick<T, "id">;
type UserUpdate = UpdatePayload<User>;
// { id: number; name?: string; email?: string; age?: number; createdAt?: Date; }
// Create payload (omit auto-generated fields)
type CreatePayload<T> = Omit<T, "id" | "createdAt">;
type UserCreate = CreatePayload<User>;
// { name: string; email: string; age: number; }
Deep Partial#
// Make all properties (including nested) optional
type DeepPartial<T> = {
[P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P];
};
interface Config {
database: {
host: string;
port: number;
credentials: {
username: string;
password: string;
};
};
cache: {
enabled: boolean;
ttl: number;
};
}
type PartialConfig = DeepPartial<Config>;
const config: PartialConfig = {
database: {
host: "localhost"
// port, credentials are optional
}
// cache is optional
};
Type-safe Object Keys#
// Better than Object.keys
function getKeys<T extends object>(obj: T): Array<keyof T> {
return Object.keys(obj) as Array<keyof T>;
}
const user = { id: 1, name: "Alice", email: "alice@example.com" };
const keys = getKeys(user); // ("id" | "name" | "email")[]
keys.forEach(key => {
console.log(user[key]); // Type-safe access
});
Combining Utility Types#
interface User {
id: number;
name: string;
email: string;
password: string;
role: "admin" | "user";
createdAt: Date;
updatedAt: Date;
}
// Public profile: pick specific fields and make readonly
type PublicProfile = Readonly<Pick<User, "id" | "name">>;
// Update DTO: omit auto-generated, make rest optional
type UpdateUserDto = Partial<Omit<User, "id" | "createdAt" | "updatedAt">>;
// Registration form: pick + make password required
type RegistrationForm = Pick<User, "name" | "email" | "password">;
// Safe user (without password)
type SafeUser = Omit<User, "password">;
// Admin user: safe user + admin role
type AdminUser = Omit<User, "password"> & { role: "admin" };
Best Practices#
-
✅ Use utility types to reduce duplication
type UpdateDto = Partial<Omit<User, "id">>; // Instead of manually redefining all fields -
✅ Combine utility types for complex transformations
type PublicUser = Readonly<Pick<User, "id" | "name">>; -
✅ Prefer utility types over manual type definitions
type UserKeys = keyof User; // Instead of: type UserKeys = "id" | "name" | "email"; -
✅ Use ReturnType for deriving types from functions
type User = ReturnType<typeof getUser>; -
❌ Don't overuse - keep types readable
// ❌ Too complex type X = Partial<Pick<Omit<Required<User>, "id">, "name" | "email">>; // ✅ Better - break it down type UserWithoutId = Omit<User, "id">; type UserBasics = Pick<UserWithoutId, "name" | "email">;
Next Steps#
- Learn about Type Guards & Narrowing
- Explore Generics to create your own utilities
- Master Modules & Namespaces for organizing code