The Utility Types You Actually Need
TypeScript ships with 20+ built-in utility types. Most tutorials list all of them with toy examples. This article covers the nine that appear in real production code every week โ with the composition patterns that make them genuinely powerful.
- You reach for
Partial<T>for update payloads and end up with everything optional includingid - You've typed the same DTO three times by hand when
Pickwould have done it in one line - You're not sure when to use
ExtractvsPickโ they sound the same but aren't - You use
Record<string, T>when you wantedRecord<SomeUnion, T>to force exhaustiveness
These nine utility types โ and their compositions โ eliminate nearly all manual type duplication in a typical TypeScript codebase.
The Core Nine
mindmap
root((Utility Types))
Object Shape
Partial
Required
Readonly
Pick
Omit
Union & Keys
Record
Extract
Exclude
Functions
ReturnType
Parameters
1. Partial and Required
interface User {
id: string;
name: string;
email: string;
avatar: string;
}
// Partial โ every property becomes optional
type UpdateUserPayload = Partial<User>;
// { id?: string; name?: string; email?: string; avatar?: string }
// Problem: id shouldn't be updatable
// Solution: Partial on just the fields you want optional
type UpdateUserPayload = Partial<Omit<User, 'id'>> & { id: string };
// { id: string; name?: string; email?: string; avatar?: string }
// Required โ reverse: make all optional properties required
interface Config {
timeout?: number;
retries?: number;
}
type ResolvedConfig = Required<Config>;
// { timeout: number; retries: number }
// Useful after you've applied defaults
function withDefaults(config: Config): ResolvedConfig {
return { timeout: 5000, retries: 3, ...config };
}2. Pick and Omit
Both create a new type from a subset of an object's properties. The difference is perspective: Pick lists what you want,Omitlists what you don't.
interface Article {
id: string;
title: string;
content: string;
authorId: string;
publishedAt: Date;
updatedAt: Date;
deletedAt: Date | null;
}
// Pick: ideal when you want a small, specific subset
type ArticleCard = Pick<Article, 'id' | 'title' | 'publishedAt'>;
// { id: string; title: string; publishedAt: Date }
// Omit: ideal when you want most of the type minus a few
type CreateArticlePayload = Omit<Article, 'id' | 'publishedAt' | 'updatedAt' | 'deletedAt'>;
// { title: string; content: string; authorId: string }
// Rule of thumb:
// Pick when you want 3 or fewer properties from a large type
// Omit when you want most of the type minus a few specific fields3. Record
Record<K, V> is not just shorthand for { [key: string]: V }. Its real power is with union key types that enforce exhaustiveness.
type Status = 'pending' | 'processing' | 'completed' | 'failed';
// โ Easy to miss a status
const labels: { [key: string]: string } = {
pending: 'Pending',
processing: 'Processing',
// forgot 'completed' and 'failed' โ no error
};
// โ
Record enforces every union member is handled
const labels: Record<Status, string> = {
pending: 'Pending',
processing: 'Processing',
completed: 'Completed',
// โ Error: property 'failed' is missing
failed: 'Failed',
};
// Also useful for caches and lookup tables
const userCache: Record<string, User> = {};
// With number keys (index signatures)
const httpMessages: Record<number, string> = {
200: 'OK',
404: 'Not Found',
500: 'Internal Server Error',
};4. Extract and Exclude
These operate on union types โ not object types. That's the key difference from Pick and Omit.
type HttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'HEAD' | 'OPTIONS';
// Extract: keep only members assignable to the second argument
type SafeMethods = Extract<HttpMethod, 'GET' | 'HEAD' | 'OPTIONS'>;
// 'GET' | 'HEAD' | 'OPTIONS'
// Exclude: remove members assignable to the second argument
type MutatingMethods = Exclude<HttpMethod, 'GET' | 'HEAD' | 'OPTIONS'>;
// 'POST' | 'PUT' | 'PATCH' | 'DELETE'
// Practical use: filtering a discriminated union
type Event =
| { type: 'click'; x: number; y: number }
| { type: 'keydown'; key: string }
| { type: 'focus'; element: string }
| { type: 'blur'; element: string };
type FocusEvents = Extract<Event, { type: 'focus' | 'blur' }>;
// { type: 'focus'; element: string } | { type: 'blur'; element: string }
type MouseEvents = Extract<Event, { type: string; x: number }>;
// { type: 'click'; x: number; y: number }5. ReturnType and Parameters
async function fetchUser(id: string): Promise<User> {
const res = await fetch(`/api/users/${id}`);
return res.json();
}
// ReturnType โ extract what a function returns
type FetchUserReturn = ReturnType<typeof fetchUser>;
// Promise<User>
// Awaited + ReturnType โ unwrap the Promise too
type ResolvedUser = Awaited<ReturnType<typeof fetchUser>>;
// User
// Parameters โ extract the parameter tuple
type FetchUserParams = Parameters<typeof fetchUser>;
// [id: string]
type FirstParam = Parameters<typeof fetchUser>[0];
// string
// Practical use: wrapper functions that preserve the signature
function withLogging<T extends (...args: any[]) => any>(
fn: T
): (...args: Parameters<T>) => ReturnType<T> {
return (...args) => {
console.log('Calling', fn.name, args);
return fn(...args);
};
}Composition Patterns
Deep Partial (for nested updates)
// Built-in Partial only goes one level deep
// For nested structures, define your own:
type DeepPartial<T> = {
[K in keyof T]?: T[K] extends object ? DeepPartial<T[K]> : T[K];
};
interface Settings {
theme: { primary: string; secondary: string };
notifications: { email: boolean; push: boolean };
}
// With built-in Partial:
type PartialSettings = Partial<Settings>;
// { theme?: { primary: string; secondary: string }; ... }
// โ Inner object is NOT partial โ you must provide all theme properties
// With DeepPartial:
type DeepPartialSettings = DeepPartial<Settings>;
// { theme?: { primary?: string; secondary?: string }; ... }
// โ
Can update just one nested fieldPick from a union to get the discriminant
type Action =
| { type: 'increment'; amount: number }
| { type: 'decrement'; amount: number }
| { type: 'reset' };
// Get just the discriminant values as a union
type ActionType = Action['type'];
// 'increment' | 'decrement' | 'reset'
// Map over them for a handler registry
type ActionHandlers = {
[K in ActionType]: (action: Extract<Action, { type: K }>) => void;
};Pitfalls
Partial makes IDs optional too
The most common mistake: using Partial<T> for update payloads when id should remain required. Always combine with Omit when fields have different optionality requirements.
Record<string, T> accepts any key
When you have a known set of keys, use a union type instead of string. Record<Status, string> catches missing entries at compile time; Record<string, string>doesn't.
Extract vs Pick confusion
Pick operates on object types (removes properties). Extract operates on union types (filters union members). Using the wrong one produces an error or never โ not a subtle type difference.

