TS ships a bunch of built-in generic types that transform other types. Knowing these is non-negotiable — they show up in every real codebase and most interviews. The good news is they’re all just mapped + conditional types underneath, so once we know those, we know how each utility works.
Object-shape transforms
These take an object type and produce a tweaked version.
type User = { id: number; name: string; email: string };
type T1 = Partial<User>; // { id?: number; name?: string; email?: string }
type T2 = Required<User>; // strips ? off every property
type T3 = Readonly<User>; // marks all properties readonly
Partial is the one we use most — perfect for PATCH endpoints and update functions where any subset of fields can be passed.
function updateUser(id: number, changes: Partial<User>) {
// can pass { name: "new" } or { email: "x", name: "y" } or anything in between
}
Pick & Omit — slice and dice
Pick<T, K> keeps only the listed keys. Omit<T, K> drops them. These are the bread and butter for deriving smaller types from a master shape.
type User = { id: number; name: string; email: string; password: string };
type PublicUser = Omit<User, "password">; // { id; name; email }
type Credentials = Pick<User, "email" | "password">; // { email; password }
In simple language: Pick is opt-in, Omit is opt-out. Reach for whichever is shorter to write — if we want most of the fields, use Omit; if we want a few, use Pick.
Record — build a uniform map
Record<K, V> builds an object type where every key in K maps to a value of type V. Think Map<K, V> but as a plain object type.
type Role = "admin" | "editor" | "viewer";
type Permissions = Record<Role, string[]>;
// { admin: string[]; editor: string[]; viewer: string[] }
const perms: Permissions = {
admin: ["read", "write", "delete"],
editor: ["read", "write"],
viewer: ["read"],
}; // TS forces us to include all three roles
Union transforms — Exclude, Extract, NonNullable
These operate on union types, not object types. The names are confusing because they’re inverses of each other.
type Status = "loading" | "success" | "error" | null;
type Done = Exclude<Status, "loading">; // "success" | "error" | null
type Finished = Extract<Status, "success" | "error">; // "success" | "error"
type Defined = NonNullable<Status>; // "loading" | "success" | "error"
The only difference is direction: Exclude removes, Extract keeps. NonNullable is just Exclude<T, null | undefined> — a shortcut for the most common case.
Function-related — ReturnType, Parameters, Awaited
These pull type info out of function and promise types using infer under the hood.
function createUser(name: string, age: number) {
return { id: 1, name, age, createdAt: new Date() };
}
type User = ReturnType<typeof createUser>;
// { id: number; name: string; age: number; createdAt: Date }
type Args = Parameters<typeof createUser>;
// [name: string, age: number]
async function loadUser() { return { id: 1, name: "Ada" }; }
type LoadedUser = Awaited<ReturnType<typeof loadUser>>;
// { id: number; name: string } — Awaited unwraps the Promise
This is the killer combo: write a function once, derive its arg and return types as needed. No duplication.
Quick reference table
| Utility | Input | Output |
|---|---|---|
| Partial<T> | { a: 1, b: 2 } | { a?: 1, b?: 2 } |
| Required<T> | { a?: 1 } | { a: 1 } |
| Readonly<T> | { a: 1 } | { readonly a: 1 } |
| Pick<T,K> | { a; b; c }, "a" | { a } |
| Omit<T,K> | { a; b; c }, "a" | { b; c } |
| Record<K,V> | "a"|"b", number | { a: number; b: number } |
| Exclude<T,U> | "a"|"b"|"c", "a" | "b" | "c" |
| Extract<T,U> | "a"|"b"|"c", "a"|"d" | "a" |
| NonNullable<T> | string | null | string |
| ReturnType<F> | () => User | User |
| Parameters<F> | (a: string) => void | [a: string] |
| Awaited<T> | Promise<User> | User |
Composing them
The real power is stacking utilities. A common pattern: build a “create” type that’s just the user-supplied fields of a record.
type DbUser = { id: number; name: string; email: string; createdAt: Date };
// Drop server-managed fields, then make everything required
type CreateUserInput = Required<Omit<DbUser, "id" | "createdAt">>;
// { name: string; email: string }
Compose these and we can model most real domain shapes without ever writing a duplicate type. That’s the entire point.