TypeScript ships a set of built-in type transformations that cover most of the "I need a slightly different version of this type" situations you'll hit. Here's the practical reference.
Partial<T> — make every field optional
interface User {
id: string;
name: string;
email: string;
}
function updateUser(id: string, changes: Partial<User>) {
// changes might only include { name: "New Name" }
}The canonical use case: update functions that accept a subset of fields to change.
Required<T> — the opposite, make every field mandatory
interface Config {
timeout?: number;
retries?: number;
}
function runWithDefaults(config: Required<Config>) {
// both fields guaranteed present at this point
}Useful after you've merged user input with defaults and want the type system to confirm nothing is missing.
Pick<T, K> — select a subset of fields
type UserPreview = Pick<User, "id" | "name">;
// { id: string; name: string }Omit<T, K> — the inverse, exclude fields
type UserWithoutEmail = Omit<User, "email">;
// { id: string; name: string }Pick and Omit solve the same problem from opposite directions — use whichever needs fewer keys listed.
Record<K, V> — a typed dictionary
type StatusColors = Record<"pending" | "active" | "done", string>;
// { pending: string; active: string; done: string }
const colors: StatusColors = {
pending: "#eab308",
active: "#22c55e",
done: "#64748b",
};Record is what you reach for instead of { [key: string]: V } when you know the exact set of valid keys — TypeScript will then error if you forget one or typo a key.
Readonly<T> — prevent reassignment
const config: Readonly<Config> = { timeout: 5000, retries: 3 };
config.timeout = 1000; // Error: Cannot assign to 'timeout' because it is a read-only propertyOnly a compile-time guarantee — it doesn't freeze the object at runtime like Object.freeze() does.
ReturnType<T> — extract a function's return type
function createUser() {
return { id: crypto.randomUUID(), createdAt: new Date() };
}
type NewUser = ReturnType<typeof createUser>;Genuinely useful when the return type is inferred and complex — you get the type without duplicating the shape by hand, and it stays in sync automatically if the function changes.
Combining them
These compose naturally:
type UserUpdate = Partial<Omit<User, "id">>;
// every field optional except id is excluded entirelyThat's the real payoff — instead of hand-writing a new interface for every variation of a shape you need, you derive it from one source of truth.