TypeScript Utility Types: Partial, Pick, Omit, Record, and Mapped Types Guide
TypeScript utility types are built-in generic types that transform existing types into new ones. Instead of rewriting type definitions, you can use Partial, Pick, Omit, Record, Required, Readonly, ReturnType, and more to create precise types with minimal code. This guide explains every essential utility type with real-world examples.
Partial, Required, and Readonly
These three utility types transform the mutability and optionality of every property at once. They are the most commonly used utility types in TypeScript codebases.
interface User {
id: number;
name: string;
email: string;
age: number;
}
// Partial<T> - makes all properties optional
type UserUpdate = Partial<User>;
// { id?: number; name?: string; email?: string; age?: number }
function updateUser(id: number, data: Partial<User>): User {
return { ...getCurrentUser(id), ...data };
}
updateUser(1, { name: 'Alice' }); // OK - only update name
// Required<T> - makes all properties required
interface Config {
host?: string;
port?: number;
timeout?: number;
}
type StrictConfig = Required<Config>;
// { host: string; port: number; timeout: number }
// Readonly<T> - makes all properties readonly
type ImmutableUser = Readonly<User>;
const user: ImmutableUser = { id: 1, name: 'Alice', email: 'a@b.com', age: 30 };
// user.name = 'Bob'; // Error: Cannot assign to 'name' because it is read-only
// Deep Readonly (custom utility)
type DeepReadonly<T> = {
readonly [K in keyof T]: T[K] extends object ? DeepReadonly<T[K]> : T[K];
};Pick, Omit, and Extract
Pick and Omit let you create new types by selecting or removing properties. Extract and Exclude operate on union types. These are essential for creating DTOs (Data Transfer Objects) and API shapes.
interface Product {
id: number;
name: string;
price: number;
stock: number;
category: string;
description: string;
}
// Pick<T, K> - select only the specified keys
type ProductCard = Pick<Product, 'id' | 'name' | 'price'>;
// { id: number; name: string; price: number }
// Useful for API response shaping
function getProductCard(p: Product): ProductCard {
return { id: p.id, name: p.name, price: p.price };
}
// Omit<T, K> - remove specified keys
type ProductForm = Omit<Product, 'id' | 'stock'>;
// { name: string; price: number; category: string; description: string }
// Exclude<T, U> - from union type, exclude assignable to U
type Direction = 'north' | 'south' | 'east' | 'west';
type Horizontal = Exclude<Direction, 'north' | 'south'>;
// 'east' | 'west'
// Extract<T, U> - from union type, keep only assignable to U
type Vertical = Extract<Direction, 'north' | 'south'>;
// 'north' | 'south'
// NonNullable<T> - removes null and undefined
type MaybeString = string | null | undefined;
type DefiniteString = NonNullable<MaybeString>;
// stringRecord and Mapped Types
Record creates object types with a fixed set of keys. Mapped types iterate over union types or object keys to transform every property systematically.
// Record<K, V> - create object type with known keys
type Role = 'admin' | 'user' | 'guest';
type Permissions = Record<Role, string[]>;
const permissions: Permissions = {
admin: ['read', 'write', 'delete'],
user: ['read', 'write'],
guest: ['read'],
};
// Record with nested types
type ApiResponse<T> = Record<string, T>;
type UserMap = Record<number, User>;
// Mapped Types - transform every property
type Nullable<T> = { [K in keyof T]: T[K] | null };
type Optional<T> = { [K in keyof T]?: T[K] };
type Stringify<T> = { [K in keyof T]: string };
// Mapped types with modifiers
type Mutable<T> = { -readonly [K in keyof T]: T[K] }; // remove readonly
type Complete<T> = { [K in keyof T]-?: T[K] }; // remove optional
// Remapping keys with 'as'
type Getters<T> = {
[K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];
};
type UserGetters = Getters<User>;
// { getId: () => number; getName: () => string; ... }ReturnType, Parameters, and Function Utilities
Function utility types let you extract type information from functions and classes without duplicating type definitions. They're especially useful when working with third-party libraries that don't export their types.
// ReturnType<T> - extract 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 }
// Parameters<T> - extract parameter types as tuple
type CreateUserParams = Parameters<typeof createUser>;
// [name: string, email: string]
// ConstructorParameters<T> - for class constructors
class Server {
constructor(public host: string, public port: number) {}
}
type ServerArgs = ConstructorParameters<typeof Server>;
// [host: string, port: number]
// InstanceType<T> - get instance type from constructor
type ServerInstance = InstanceType<typeof Server>;
// Server
// Awaited<T> - unwrap Promise types
type UserPromise = Promise<User>;
type ResolvedUser = Awaited<UserPromise>;
// User
async function fetchUser(): Promise<User> { /* ... */ return {} as User; }
type FetchResult = Awaited<ReturnType<typeof fetchUser>>;
// UserConditional Types and Infer
Conditional types express if-else logic at the type level. The infer keyword lets you capture and name types within conditional expressions, enabling powerful type extraction patterns.
// Conditional Types
type IsArray<T> = T extends any[] ? true : false;
type CheckString = IsArray<string>; // false
type CheckArray = IsArray<string[]>; // true
// infer - extract type from generic
type UnpackArray<T> = T extends (infer U)[] ? U : T;
type StringItem = UnpackArray<string[]>; // string
type NumberItem = UnpackArray<number>; // number
// Extract Promise value
type UnpackPromise<T> = T extends Promise<infer U> ? U : T;
type PromiseValue = UnpackPromise<Promise<number>>; // number
// Get first element of tuple
type First<T extends any[]> = T extends [infer F, ...any[]] ? F : never;
type Head = First<[string, number, boolean]>; // string
// Distributive conditional types
type Flatten<T> = T extends Array<infer U> ? U : T;
type Mixed = Flatten<string[] | number | boolean[]>;
// string | number | booleanCombining Utility Types
The real power of utility types comes from combining them. Here's a real-world example showing how to build a complete API type system from a single source type:
// Real-world example: API layer with combined utility types
interface ApiUser {
id: number;
username: string;
email: string;
password: string; // should never leave server
createdAt: string;
updatedAt: string;
}
// Safe user for frontend (strip sensitive fields)
type SafeUser = Omit<ApiUser, 'password'>;
// User creation form (no server-generated fields)
type CreateUserDto = Omit<ApiUser, 'id' | 'createdAt' | 'updatedAt'>;
// Patch request - all fields optional except id
type PatchUserDto = Partial<Omit<ApiUser, 'id'>> & Pick<ApiUser, 'id'>;
// Validation errors map
type ValidationErrors<T> = Partial<Record<keyof T, string>>;
type UserErrors = ValidationErrors<CreateUserDto>;
// { username?: string; email?: string; password?: string }
// Generic API response wrapper
type ApiResult<T, E = string> =
| { success: true; data: T }
| { success: false; error: E };
type UserResult = ApiResult<SafeUser>;Frequently Asked Questions
What is the difference between Partial<T> and Optional<T>?
Partial<T> is a built-in TypeScript utility type that makes all properties of T optional. Optional<T> is not built-in — it's a common name for custom mapped types that do the same thing. You should always prefer the built-in Partial<T> over writing your own.
When should I use Pick vs Omit?
Use Pick when you want to select a small subset of properties from a large type (you know what you WANT). Use Omit when you want most properties but need to exclude a few (you know what you DON'T WANT). For removing 1-2 properties, Omit is cleaner. For selecting 1-2 properties, Pick is cleaner.
What is the difference between Record<string, T> and { [key: string]: T }?
They are nearly identical in behavior, but Record<string, T> is more readable and the idiomatic TypeScript style. Record<string, T> also plays better with other utility types. One subtle difference: Record<never, T> produces an empty object type {}, while the index signature always allows any string key.
How do I create a deep Partial type?
TypeScript's built-in Partial<T> is shallow — it only makes the top level optional. For deep partial, create a recursive utility type: type DeepPartial<T> = { [K in keyof T]?: T[K] extends object ? DeepPartial<T[K]> : T[K] }. Libraries like ts-toolbelt provide this and many more deep utility types.
What are template literal types and how do they relate to mapped types?
Template literal types (introduced in TS 4.1) let you create string types with embedded type expressions: type EventName<T extends string> = `on${Capitalize<T>}`. Combined with mapped types, you can generate entire interfaces of event handlers, getters, setters, etc., from a single source-of-truth type.
Can I use utility types with interfaces and type aliases both?
Yes. TypeScript utility types work with both interface and type alias definitions interchangeably. Partial<MyInterface> and Partial<MyType> both work the same way. The choice between interface and type alias is mostly stylistic, though interfaces support declaration merging while type aliases do not.
Conclusion
TypeScript utility types are force multipliers for your type system. Instead of maintaining dozens of similar interfaces that drift out of sync, define one source-of-truth type and derive all variants with Partial, Pick, Omit, and friends. Your codebase becomes easier to maintain and refactor as requirements change.
Start by auditing your codebase for interfaces that are near-duplicates of each other — those are prime candidates for utility type refactoring. Tools like the viadreams.cc JSON formatter and diff tool can help you compare complex type structures visually.