TypeScript's built-in utility types are powerful tools that help you write cleaner, more maintainable code. In this guide, I'll walk through the most commonly used utility types with practical examples from real-world applications.
',Required
description: 'Perfect for update flows where callers only send the fields they want to change.',
bullets: ['Form patches', 'Update DTOs', 'Feature-flagged config overrides'],
tone: 'success'
},
{
eyebrow: 'STRICTNESS',
title: '',Pick
description: 'Useful when a loose input shape becomes a guaranteed runtime shape after defaults or validation.',
bullets: ['Normalized config', 'Post-validation objects', 'Internal invariants'],
tone: 'info'
},
{
eyebrow: 'SHAPING',
title: 'andOmit',Record
description: 'The fastest way to carve focused view models and payload types out of larger domain types.',
bullets: ['Preview cards', 'Create/update payloads', 'Public vs internal fields'],
tone: 'warning'
},
{
eyebrow: 'MAPS',
title: '',Readonly
description: 'Great for dictionaries, lookup tables, and keyed collections where the value shape is consistent.',
bullets: ['Role maps', 'Route registries', 'Status-to-label maps'],
tone: 'violet'
},
{
eyebrow: 'SAFETY',
title: 'andNonNullable',ReturnType
description: 'These types help lock down accidental mutation and strip nullable cases after checks or normalization.',
bullets: ['Immutable config objects', 'Derived non-null props', 'Safer shared state'],
tone: 'success'
},
{
eyebrow: 'INFERENCE',
title: 'andAwaited`',
description: 'Use them to extract shapes from real functions instead of manually duplicating types that will drift.',
bullets: ['Async loader results', 'Factory output types', 'API wrapper return values'],
tone: 'info'
}
]}
/>
Why Utility Types Matter
When building large-scale applications like the ones I work on at Expedia Group, type safety isn't just nice to have — it's essential. Utility types help you derive new types from existing ones without duplication.
Partial<T>
Makes all properties of T optional. This is incredibly useful for update functions.
`typescript
interface User {
id: string;
name: string;
email: string;
role: 'admin' | 'user';
}
function updateUser(id: string, updates: Partial) {
// Only update the fields that were provided
}
updateUser('123', { name: 'Umesh' }); // Valid!
`
Required<T>
The opposite of Partial — makes all properties required.
`typescript
interface Config {
host?: string;
port?: number;
debug?: boolean;
}
const defaultConfig: Required = {
host: 'localhost',
port: 3000,
debug: false,
};
`
Pick<T, K>
Creates a type with only the specified properties.
`typescript
type UserPreview = Pick;
// Equivalent to:
// { id: string; name: string }
`
Omit<T, K>
Creates a type excluding the specified properties.
`typescript
type CreateUserInput = Omit;
// Everything except id
`
Record<K, T>
Creates a type with keys of type K and values of type T.
`typescript
type UserRoles = Record;
const roleMap: UserRoles = {
admin: [/* admin users /],
editor: [/ editor users */],
};
`
Practical Example: API Response Types
Here's how I combine these utility types in real projects:
`typescript
interface ApiResponse {
data: T;
status: number;
message: string;
}
type UserListResponse = ApiResponse[]>;
type UserUpdatePayload = Partial>;
`
A Few More Utility Types Worth Knowing
Readonly<T>
Use Readonly when an object should never be mutated after creation.
`typescript
interface FeatureFlags {
newSearch: boolean;
redesignedCheckout: boolean;
}
const flags: Readonly = {
newSearch: true,
redesignedCheckout: false,
};
`
NonNullable<T>
Strip null and undefined once you've validated a value.
typescript
type MaybeUser = User | null | undefined;
type SafeUser = NonNullable;
ReturnType<T> and Awaited<T>
Infer the result shape from real functions instead of duplicating it by hand.
`typescript
async function fetchCurrentUser() {
return { id: '123', name: 'Umesh', role: 'admin' as const };
}
type FetchCurrentUserResult = Awaited>;
`
Key Takeaways
- Use
Partialfor update operations where not all fields are required - Use
PickandOmitto create focused types from larger interfaces - Use
Recordfor dictionary-like structures -
Readonly,NonNullable,ReturnType, andAwaitedcover a lot of everyday type work - Combine utility types for complex transformations
- These types are zero-cost at runtime — they only exist during compilation
Mastering these utility types will significantly improve your TypeScript code quality and developer experience.
Originally published at umesh-malik.com
Top comments (0)