TypeScript ships with powerful built-in type helpers that transform existing types into new ones β without writing them from scratch.
Utility types are generic types built into TypeScript that let you construct new types based on existing ones. Instead of duplicating type definitions, you compose them.
| 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> | Picks only specified keys from T |
| Omit<T, K> | Removes specified keys from T |
| Record<K, V> | Creates an object type with keys K and values V |
| Exclude<T, U> | Removes types in U from union T |
| Extract<T, U> | Keeps only types in T that match U |
| NonNullable<T> | Removes null and undefined from T |
| ReturnType<T> | Gets the return type of a function |
Makes all properties optional. Perfect for update functions where you only want to pass the fields you're changing.
interface User { name: string; age: number; email: string; role: string; } // Without Partial β you'd need all fields every time function updateUser(id: number, data: Partial<User>): void { // data can have any subset of User fields } updateUser(1, { name: "Alice" }); // β just name updateUser(2, { email: "[email protected]" }); // β just email updateUser(3, { age: 30, role: "admin" }); // β two fields // Partial<User> is equivalent to: type PartialUser = { name?: string; age?: number; email?: string; role?: string; };
The opposite of Partial β makes every property required, even optional ones.
interface Config { host?: string; port?: number; debug?: boolean; } // Before saving to DB, ensure everything is set function saveConfig(cfg: Required<Config>): void { console.log(cfg.host); // guaranteed to exist console.log(cfg.port); // guaranteed to exist } // saveConfig({ host: "localhost" }); β port and debug missing saveConfig({ host: "localhost", port: 3000, debug: false }); // β
Pick creates a type with only the keys you want. Omit creates a type with specific keys removed. They're inverses of each other.
interface User { id: number; name: string; email: string; password: string; // sensitive! createdAt: Date; } // Pick only what the UI needs to show type UserCard = Pick<User, "id" | "name" | "email">; // { id: number; name: string; email: string } // Omit sensitive fields before sending to client type PublicUser = Omit<User, "password">; // { id, name, email, createdAt } β password removed! function getPublicProfile(user: User): PublicUser { const { password, ...rest } = user; return rest; }
π‘ Rule of thumb: Use Pick when you want a few keys from a large type. Use Omit when you want all keys except a few.
Creates an object type where all keys are type K and all values are type V. Great for maps, dictionaries, and lookups.
// Map string keys to numbers const scores: Record<string, number> = { alice: 95, bob: 87, charlie: 91, }; // Enforce specific keys with a union type type Fruit = "apple" | "banana" | "orange"; const inventory: Record<Fruit, number> = { apple: 50, banana: 30, orange: 20, // "grape": 10 β not in Fruit union }; // Record with object values interface PageInfo { title: string; url: string; } type Pages = Record<string, PageInfo>; const routes: Pages = { home: { title: "Home", url: "/" }, about: { title: "About", url: "/about" }, };
type Animal = "cat" | "dog" | "fish" | "bird"; // Remove "fish" from the union type LandAnimal = Exclude<Animal, "fish">; // "cat" | "dog" | "bird" // Keep only pets that can cuddle type CuddlyPet = Extract<Animal, "cat" | "dog">; // "cat" | "dog" // Remove null and undefined type MaybeString = string | null | undefined; type DefiniteString = NonNullable<MaybeString>; // string // ReturnType extracts what a function returns function getUser() { return { name: "Alice", age: 25 }; } type UserData = ReturnType<typeof getUser>; // { name: string; age: number }
The real power comes from composing multiple utility types together:
interface Article { id: number; title: string; body: string; author: string; publishedAt: Date; tags: string[]; } // Only the fields needed for article creation (no id/publishedAt yet) type CreateArticle = Omit<Article, "id" | "publishedAt">; // Patch endpoint β any subset of CreateArticle fields type UpdateArticle = Partial<CreateArticle>; // Preview card β just title, author, tags type ArticlePreview = Readonly<Pick<Article, "title" | "author" | "tags">>;