Table of Contents
TypeScriptβs utility types are powerful tools that transform existing types into new ones. They reduce boilerplate, improve type safety, and make your code more expressive. Letβs explore every built-in utility type and learn to create custom ones.
Partial and Required
Partial<T>
Makes all properties optional:
interface User {
id: number;
name: string;
email: string;
avatar: string;
}
// All properties become optional
type PartialUser = Partial<User>;
// Equivalent to:
// {
// id?: number;
// name?: string;
// email?: string;
// avatar?: string;
// }
// Perfect for update functions
function updateUser(id: number, updates: Partial<User>): User {
const user = getUserById(id);
return { ...user, ...updates };
}
updateUser(1, { name: 'New Name' }); // Only update name
Required<T>
Makes all properties required (opposite of Partial):
interface Config {
apiKey?: string;
timeout?: number;
retries?: number;
}
type RequiredConfig = Required<Config>;
// All properties now required:
// {
// apiKey: string;
// timeout: number;
// retries: number;
// }
function initializeApp(config: RequiredConfig): void {
// All values guaranteed to exist
console.log(config.apiKey, config.timeout, config.retries);
}
Pick and Omit
Pick<T, K>
Creates a type with only selected properties:
interface Article {
id: number;
title: string;
content: string;
author: string;
publishedAt: Date;
views: number;
}
// Only pick what you need
type ArticlePreview = Pick<Article, 'id' | 'title' | 'author'>;
// {
// id: number;
// title: string;
// author: string;
// }
function renderPreview(article: ArticlePreview): JSX.Element {
return (
<div>
<h2>{article.title}</h2>
<span>By {article.author}</span>
</div>
);
}
Omit<T, K>
Creates a type excluding selected properties:
// Remove sensitive fields
type PublicUser = Omit<User, 'password' | 'apiKey' | 'internalNotes'>;
// For API responses
type UserResponse = Omit<User, 'password'>;
// For creation (omit auto-generated fields)
type CreateUserInput = Omit<User, 'id' | 'createdAt' | 'updatedAt'>;
Record and Readonly
Record<K, V>
Creates an object type with specific key and value types:
// String keys with User values
type UserMap = Record<string, User>;
const users: UserMap = {
'user-1': { id: 1, name: 'Alice', email: 'alice@example.com' },
'user-2': { id: 2, name: 'Bob', email: 'bob@example.com' },
};
// Literal union keys
type Role = 'admin' | 'editor' | 'viewer';
type Permissions = Record<Role, string[]>;
const permissions: Permissions = {
admin: ['read', 'write', 'delete', 'manage-users'],
editor: ['read', 'write'],
viewer: ['read'],
};
// Status colors
type Status = 'success' | 'warning' | 'error';
type StatusColors = Record<Status, string>;
const colors: StatusColors = {
success: '#10B981',
warning: '#F59E0B',
error: '#EF4444',
};
Readonly<T>
Makes all properties read-only:
interface State {
user: User | null;
isLoading: boolean;
error: string | null;
}
type ReadonlyState = Readonly<State>;
const state: ReadonlyState = {
user: null,
isLoading: false,
error: null,
};
// state.isLoading = true; // Error: Cannot assign to 'isLoading'
Extract and Exclude
Extract<T, U>
Extracts types from a union that are assignable to U:
type AllEvents =
| { type: 'click'; x: number; y: number }
| { type: 'scroll'; offset: number }
| { type: 'keydown'; key: string }
| { type: 'focus' }
| { type: 'blur' };
// Extract events with coordinates
type MouseEvents = Extract<AllEvents, { x: number }>;
// { type: 'click'; x: number; y: number }
// Extract by type literal
type ClickEvent = Extract<AllEvents, { type: 'click' }>;
Exclude<T, U>
Removes types from a union:
type Primitive = string | number | boolean | null | undefined;
// Remove null and undefined
type NonNullablePrimitive = Exclude<Primitive, null | undefined>;
// string | number | boolean
// Filter event types
type NonMouseEvents = Exclude<AllEvents, { x: number }>;
// All events except click
NonNullable and ReturnType
NonNullable<T>
Removes null and undefined from a type:
type MaybeString = string | null | undefined;
type DefiniteString = NonNullable<MaybeString>;
// string
function processValue(value: MaybeString): void {
// value could be null or undefined here
if (value != null) {
// TypeScript knows value is string here
const definite: NonNullable<typeof value> = value;
console.log(definite.toUpperCase());
}
}
ReturnType<T>
Extracts the return type of a function:
function createUser(name: string, email: string) {
return {
id: Math.random(),
name,
email,
createdAt: new Date(),
};
}
type CreatedUser = ReturnType<typeof createUser>;
// {
// id: number;
// name: string;
// email: string;
// createdAt: Date;
// }
// Useful for async functions too
async function fetchUsers(): Promise<User[]> {
// ...
}
type FetchUsersResult = Awaited<ReturnType<typeof fetchUsers>>;
// User[]
Parameters and ConstructorParameters
Parameters<T>
Extracts function parameter types as a tuple:
function greet(name: string, age: number, isVip?: boolean): string {
return `Hello, ${name}!`;
}
type GreetParams = Parameters<typeof greet>;
// [name: string, age: number, isVip?: boolean]
// Use with spread
function logAndGreet(...args: Parameters<typeof greet>): void {
console.log('Calling greet with:', args);
greet(...args);
}
ConstructorParameters<T>
Extracts constructor parameter types:
class ApiClient {
constructor(
private baseUrl: string,
private apiKey: string,
private timeout: number = 5000
) {}
}
type ApiClientParams = ConstructorParameters<typeof ApiClient>;
// [baseUrl: string, apiKey: string, timeout?: number]
function createApiClient(...args: ApiClientParams): ApiClient {
return new ApiClient(...args);
}
Creating Custom Utility Types
DeepPartial
Make all nested properties optional:
type DeepPartial<T> = {
[P in keyof T]?: T[P] extends object
? DeepPartial<T[P]>
: T[P];
};
interface NestedConfig {
database: {
host: string;
port: number;
credentials: {
username: string;
password: string;
};
};
cache: {
enabled: boolean;
ttl: number;
};
}
type PartialConfig = DeepPartial<NestedConfig>;
const config: PartialConfig = {
database: {
host: 'localhost',
// port, credentials all optional
},
// cache entirely optional
};
DeepReadonly
Make all nested properties readonly:
type DeepReadonly<T> = {
readonly [P in keyof T]: T[P] extends object
? DeepReadonly<T[P]>
: T[P];
};
const frozenConfig: DeepReadonly<NestedConfig> = {
database: {
host: 'localhost',
port: 5432,
credentials: {
username: 'admin',
password: 'secret',
},
},
cache: {
enabled: true,
ttl: 3600,
},
};
// frozenConfig.database.port = 3306; // Error!
// frozenConfig.database.credentials.password = 'new'; // Error!
Mutable
Remove readonly from all properties:
type Mutable<T> = {
-readonly [P in keyof T]: T[P];
};
interface ReadonlyUser {
readonly id: number;
readonly name: string;
}
type MutableUser = Mutable<ReadonlyUser>;
// {
// id: number;
// name: string;
// }
RequireAtLeastOne
Require at least one property from a set:
type RequireAtLeastOne<T, Keys extends keyof T = keyof T> =
Pick<T, Exclude<keyof T, Keys>> &
{
[K in Keys]-?: Required<Pick<T, K>> & Partial<Pick<T, Exclude<Keys, K>>>;
}[Keys];
interface SearchParams {
query?: string;
category?: string;
tags?: string[];
}
type ValidSearch = RequireAtLeastOne<SearchParams>;
// const search: ValidSearch = {}; // Error!
const search: ValidSearch = { query: 'typescript' }; // OK
Branded Types
Create nominal types for type safety:
type Brand<T, B> = T & { __brand: B };
type UserId = Brand<string, 'UserId'>;
type OrderId = Brand<string, 'OrderId'>;
function getUser(id: UserId): User {
// ...
}
function getOrder(id: OrderId): Order {
// ...
}
const userId = 'user-123' as UserId;
const orderId = 'order-456' as OrderId;
getUser(userId); // OK
// getUser(orderId); // Error! Type mismatch
Practical Examples
API Response Types
// Base response
interface ApiResponse<T> {
data: T;
status: number;
message: string;
}
// Paginated response
interface PaginatedResponse<T> extends ApiResponse<T[]> {
pagination: {
page: number;
perPage: number;
total: number;
totalPages: number;
};
}
// Error response
type ErrorResponse = Omit<ApiResponse<null>, 'data'> & {
error: {
code: string;
details?: Record<string, string[]>;
};
};
Form State Types
interface FormField<T> {
value: T;
error: string | null;
touched: boolean;
dirty: boolean;
}
type FormState<T> = {
[K in keyof T]: FormField<T[K]>;
};
interface LoginForm {
email: string;
password: string;
rememberMe: boolean;
}
type LoginFormState = FormState<LoginForm>;
const form: LoginFormState = {
email: { value: '', error: null, touched: false, dirty: false },
password: { value: '', error: null, touched: false, dirty: false },
rememberMe: { value: false, error: null, touched: false, dirty: false },
};
Conclusion
TypeScript utility types are essential for:
- Reducing boilerplate: Transform types instead of redefining
- Type safety: Catch errors at compile time
- Expressiveness: Clearly communicate intent
- Maintainability: Single source of truth for types
Master these utilities and youβll write more robust TypeScript code with less effort.
Whatβs your favorite TypeScript utility type? Share in the comments!
In-Article Ad
Dev Mode
Alex Chen
Senior Full-Stack Developer
I'm a passionate full-stack developer with 10+ years of experience building scalable web applications. I write about Vue.js, Node.js, PostgreSQL, and modern DevOps practices.
Enjoyed this article?
Subscribe to get more tech content delivered to your inbox.
Related Articles
Pinia State Management: The Complete Vue 3 Guide
Master Pinia, the official Vue 3 state management library. Learn stores, actions, getters, plugins, and best practices.
Vue 3 Composition API: 10 Best Practices for Clean Code
Master the Vue 3 Composition API with these essential best practices. Learn how to write maintainable, reusable, and performant Vue components.
Building a Production-Ready REST API with Node.js and TypeScript
Learn to build scalable REST APIs with Node.js, Express, TypeScript, and PostgreSQL. Includes authentication, validation, and error handling.