TypeScript utility types are generic types that ship with the language and transform one type into another. Partial<User> makes every property optional, Pick<User, 'id'> keeps only some, and Omit<User, 'password'> removes some. They save you from rewriting near-duplicate interfaces, and they are the fastest way to keep types in sync with a single source of truth. Here are the ones I use constantly, with examples, and a few custom helpers that fill the gaps.
I originally wrote this in 2023 and have updated it to cover newer additions like NoInfer.
TypeScript Utility Types at a Glance
| Utility type | What it does |
|---|---|
Partial<T> | Makes all properties optional |
Required<T> | Makes all properties required |
Readonly<T> | Makes all properties read-only |
Pick<T, K> | Keeps only the listed keys |
Omit<T, K> | Removes the listed keys |
Record<K, V> | Object type with keys K and values V |
Exclude<U, X> | Removes members from a union |
Extract<U, X> | Keeps members of a union |
NonNullable<T> | Removes null and undefined |
ReturnType<F> | The return type of a function |
Parameters<F> | The parameter types of a function as a tuple |
Awaited<T> | The resolved type of a Promise |
NoInfer<T> | Blocks type inference from a position (TypeScript 5.4+) |
Partial<Type>
Partial creates a new type where every property is optional. Use it for update payloads and optional configuration objects, something you see everywhere in typed ORM layers like Drizzle and Prisma.
interface User {
id: number;
name: string;
email: string;
}
type PartialUser = Partial<User>;
// { id?: number; name?: string; email?: string }Required<Type> is the inverse: it removes the ? from every property.
Readonly<Type>
Readonly marks every property as read-only, so assignments to them are compile errors. It is handy for configuration and state you never want to mutate.
interface Config {
url: string;
apiKey: string;
}
type ReadonlyConfig = Readonly<Config>;Pick<Type, Keys>
Pick builds a new type by selecting specific properties. Use it to expose only a subset of a large object.
interface User {
id: number;
name: string;
email: string;
password: string;
}
type PublicUser = Pick<User, 'id' | 'name' | 'email'>;Omit<Type, Keys>
Omit is the opposite of Pick: it excludes properties. It is the one I reach for to strip sensitive fields.
type UserWithoutPassword = Omit<User, 'password'>;Record<Keys, Type>
Record creates an object type with a given set of keys and a single value type. It is a clean way to type dictionaries and lookup tables.
type Role = 'admin' | 'editor' | 'viewer';
const permissions: Record<Role, string[]> = {
admin: ['read', 'write', 'delete'],
editor: ['read', 'write'],
viewer: ['read'],
};Because the keys are a union, TypeScript errors if you forget a role.
Exclude, Extract and NonNullable
These three work on unions.
type Status = 'idle' | 'loading' | 'error' | 'done';
type Active = Exclude<Status, 'idle'>; // 'loading' | 'error' | 'done'
type Failed = Extract<Status, 'error' | 'timeout'>; // 'error'
type Name = NonNullable<string | null | undefined>; // stringReturnType and Parameters
ReturnType extracts what a function returns. Parameters extracts its arguments as a tuple. Both are useful when you want to type something from a function you do not control.
function getUser(id: number): { id: number; name: string } {
return { id, name: 'Alex' };
}
type UserResult = ReturnType<typeof getUser>;
type GetUserArgs = Parameters<typeof getUser>; // [id: number]Awaited<Type>
Awaited unwraps a Promise, including nested ones. Combine it with ReturnType to get the resolved value of an async function:
async function fetchUser(): Promise<User> {
// ...
return {} as User;
}
type FetchedUser = Awaited<ReturnType<typeof fetchUser>>; // UserNote that ReturnType<typeof fetchUser> alone is Promise<User>, not User. That is the mistake Awaited fixes.
NoInfer<Type>
NoInfer (TypeScript 5.4) stops a parameter from being used to infer a generic. Without it, a default value can widen the type:
function createStore<T extends string>(values: T[], initial: NoInfer<T>) {
return { values, current: initial };
}
createStore(['a', 'b'], 'c'); // error: 'c' is not 'a' | 'b'Without NoInfer, TypeScript would infer T as 'a' | 'b' | 'c' and accept the call.
Custom Utility Types Worth Writing
The built-ins do not cover everything. Two helpers I add to most projects:
// Makes every property optional, recursively
type DeepPartial<T> = {
[K in keyof T]?: T[K] extends object ? DeepPartial<T[K]> : T[K];
};
// Flattens intersections so hover tooltips show a readable type
type Prettify<T> = {
[K in keyof T]: T[K];
} & {};DeepPartial is useful for nested config overrides. Prettify has no runtime effect. It just makes editor tooltips show one flat object instead of A & B & C.
A Performance Warning
Deeply recursive utility types like DeepPartial or DeepReadonly that walk an unbounded tree are where much of the large-codebase compile-time pain comes from. Use them on small, well-bounded shapes, and read the TypeScript performance guide before they sneak into hot paths. Utility types are erased at runtime, so they cost nothing when the program runs, but they do cost compiler time. If compile time is the bottleneck, TypeScript 7's Go compiler helps too.
Frequently Asked
What are TypeScript utility types? Built-in generic types such as Partial, Pick, Omit and Record that transform existing types, so you do not have to write variants by hand.
What is the difference between Pick and Omit? Pick keeps the keys you list. Omit removes them. Use whichever list is shorter.
What is the difference between Partial and Required? Partial makes every property optional. Required makes every property mandatory.
How do I get the resolved type of an async function? Use Awaited<ReturnType<typeof fn>>.
Do utility types affect runtime performance? No. Types are erased at compile time, though complex recursive types can slow down the compiler.