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.
Exclude<T, U> and Extract<T, U> — filtering union types
type Status = "pending" | "active" | "done" | "archived";
type ActiveStatus = Exclude<Status, "archived">;
// "pending" | "active" | "done"
type FinalStatus = Extract<Status, "done" | "archived">;
// "done" | "archived"Exclude removes members of a union that match; Extract keeps only the members that match — the same relationship as Omit/Pick, but operating on union types instead of object shapes.
NonNullable<T> — strip out null and undefined
function getLength(value: string | null | undefined): number {
const safe: NonNullable<typeof value> = value ?? "";
return safe.length;
}Most useful after a null-check or default value, to tell the type system explicitly that null/undefined are no longer possible at that point — even when the compiler's own narrowing doesn't already infer it.
Awaited<T> — unwrap a Promise's resolved type
async function fetchUser() {
return { id: "1", name: "Ada" };
}
type User = Awaited<ReturnType<typeof fetchUser>>;
// { id: string; name: string } — not Promise<{ id: string; name: string }>Handles nested promises correctly too (Promise<Promise<T>> resolves to T), which matters because awaiting a promise that itself resolves to a promise flattens automatically at runtime — Awaited mirrors that at the type level.
| Utility | What it does |
|---|---|
Partial<T> | Every field becomes optional |
Required<T> | Every field becomes mandatory |
Pick<T, K> / Omit<T, K> | Select or exclude specific fields |
Record<K, V> | A typed dictionary with known keys |
Readonly<T> | Compile-time-only immutability |
ReturnType<T> / Awaited<T> | Extract a function's (resolved) return type |
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.
These utility types earn their keep most visibly during a real incremental JavaScript-to-TypeScript migration — deriving new shapes from existing ones instead of hand-writing near-duplicate interfaces is exactly the leverage that makes converting a large codebase file by file tractable.
Parameters<T> and ConstructorParameters<T>
Two less commonly needed but genuinely useful extraction utilities, for when you need a function or class constructor's argument types rather than its return type:
function createUser(name: string, age: number, active: boolean) {
return { name, age, active };
}
type CreateUserArgs = Parameters<typeof createUser>;
// [name: string, age: number, active: boolean]
class ApiClient {
constructor(baseUrl: string, timeout: number) {}
}
type ApiClientArgs = ConstructorParameters<typeof ApiClient>;
// [baseUrl: string, timeout: number]These come up most often when writing a wrapper function that needs to accept "whatever arguments the wrapped function accepts" without duplicating the parameter list by hand — a logging wrapper, a retry wrapper, or a factory function that forwards its arguments to an underlying constructor.
InstanceType<T> — the type a constructor produces
The counterpart to ConstructorParameters — instead of extracting what a class constructor accepts, this extracts what it produces:
type ApiClientInstance = InstanceType<typeof ApiClient>;
// the actual instance type, equivalent to just writing "ApiClient" here,
// but useful when working generically with a class passed as a valueThis matters specifically in generic code that receives a class itself as a parameter (a factory pattern, a dependency injection container) rather than a fixed, named class — InstanceType<T> lets that generic code express "the type this class produces" without knowing the concrete class name ahead of time.
Common mistakes
- Reaching for
Partial<T>on a type that should never actually have optional fields at runtime — it's meant for describing an update payload, not for weakening a type just to make an error go away. - Confusing
Readonly<T>'s compile-time-only guarantee with real immutability, then being surprised an object still mutates through a differently-typed reference to the same underlying object. - Nesting
Omit/Pickseveral levels deep instead of defining an intermediate named type — technically works, but becomes unreadable in error messages and hover tooltips past two or three levels of composition. - Using
Record<string, V>when the actual key set is known and finite — this silently allows any string key and loses the "did you typo a key" safety that a union of literal keys would have caught.
Related reading
- How to Migrate a JavaScript Codebase to TypeScript Incrementally — shares tags: typescript, javascript, programming (same category).
- The JavaScript Event Loop, Explained With Diagrams — shares tags: javascript, programming.
- Modern Array Methods You Should Be Using Instead of Loops — shares tags: javascript, programming.
- Async Python with asyncio: A Practical Introduction — shares tags: programming.
- Big O Notation Without the Math Panic — shares tags: programming.