Most TypeScript codebases are full of types that are copies of other types with one field changed. A User, then a UserUpdate that's the same minus id, then a UserResponse that's the same plus a timestamp. Maintaining those by hand is how types drift out of sync with reality.
TypeScript ships a set of built-in utility types that derive new types from existing ones, so a change in one place propagates everywhere. Here are the ten I reach for constantly, with the real situations they solve.
1. Partial<T> — everything optional
Perfect for update payloads and patch functions where the caller supplies only the fields they're changing.
interface User { id: string; name: string; email: string; }
function updateUser(id: string, changes: Partial<User>) { /* ... */ }
updateUser("1", { email: "new@example.com" }); // ✅ no need to pass name
2. Required<T> — everything mandatory
The inverse. Useful when you receive a loosely-typed config and want to assert that defaults have filled every hole.
interface Options { timeout?: number; retries?: number; }
function run(opts: Required<Options>) { /* every field guaranteed */ }
3. Readonly<T> — freeze the shape at the type level
Signals intent and catches accidental mutation at compile time — great for state objects and function parameters you don't own.
function render(config: Readonly<User>) {
config.name = "x"; // ❌ Cannot assign to 'name', it is read-only
}
4. Pick<T, K> — keep only the keys you need
Stop hand-copying a subset of fields. Pick derives it.
type UserPreview = Pick<User, "id" | "name">; // { id: string; name: string }
5. Omit<T, K> — everything except these keys
The one I use most. "A User but without the server-generated id" becomes a one-liner that updates itself when User changes.
type NewUser = Omit<User, "id">; // { name: string; email: string }
6. Record<K, V> — typed dictionaries
Replaces the vague { [key: string]: V } with something precise, and can constrain the keys to a union.
type Role = "admin" | "editor" | "viewer";
const permissions: Record<Role, string[]> = {
admin: ["read", "write", "delete"],
editor: ["read", "write"],
viewer: ["read"],
}; // miss a role and the compiler tells you
7. ReturnType<T> — infer what a function returns
Invaluable when a function's return type is complex or inferred, and you want a variable typed to match without restating it.
function createStore() { return { count: 0, items: [] as string[] }; }
type Store = ReturnType<typeof createStore>;
8. Parameters<T> — capture an argument list as a tuple
Great for wrappers, decorators, and higher-order functions that must forward arguments faithfully.
type LogArgs = Parameters<typeof updateUser>; // [string, Partial<User>]
function withLogging(...args: Parameters<typeof updateUser>) {
console.log("calling updateUser", args);
return updateUser(...args);
}
9. NonNullable<T> — strip null and undefined
After a guard, narrow a type so the rest of the function doesn't have to keep checking.
type MaybeUser = User | null | undefined;
type DefiniteUser = NonNullable<MaybeUser>; // User
10. Exclude<T, U> and Extract<T, U> — filter unions
Filter members out of (or into) a union type — the set operations of the type system.
type Status = "active" | "archived" | "deleted";
type Visible = Exclude<Status, "deleted">; // "active" | "archived"
type Gone = Extract<Status, "deleted">; // "deleted"
Compose them for real power
The utilities shine when combined. "A draft is a User without its id, with everything optional" is one expression that tracks User forever:
type UserDraft = Partial<Omit<User, "id">>;
// A response type derived from the model, plus a server field:
type UserResponse = Readonly<User & { createdAt: string }>;
Once your derived types are computed from a single source of truth, an edit to User ripples through UserDraft, UserPreview, and UserResponse automatically — and the compiler flags every place that needs attention.
A working reference beats memorizing
These ten cover the overwhelming majority of day-to-day needs, but the combinations (mapped types, conditional types, template literal types) are where teams reinvent the same helpers. If you'd rather pull from a vetted set, the TypeScript Utility Library collects production-ready type helpers and patterns — deep-partial, strict-omit, branded types, and more — so you stop rewriting them per project.
Takeaway
The fastest way to a cleaner TypeScript codebase isn't more types — it's fewer hand-written ones. Derive types from a single source with Pick, Omit, Partial, and friends, and let the compiler keep them honest as your models evolve.
Top comments (0)