Utility types
TypeScript ships a set of built-in generic types for the most common transformations you'd otherwise write by hand — making every field optional, picking a subset of fields, building a type from a list of keys. Recognizing them on sight saves real, repetitive work.
4 min read
Partial<T>: every field becomes optional
interface User {
id: number;
name: string;
email: string;
}
function updateUser(id: number, changes: Partial<User>) {
// changes might have any subset of User's fields, or none at all
}
updateUser(1, { name: "New Name" }); // fine — email and id aren't required herePartial<User> produces a new type identical to User, except every field is marked optional (?). This is the standard shape for a "patch" or "update" function — the caller supplies only the fields they actually want to change, and writing Partial<User> avoids manually redeclaring every field of User a second time, just with ? added to each one.
Pick<T, K> and Omit<T, K>: selecting or excluding specific fields
type UserPreview = Pick<User, "id" | "name">;
// { id: number; name: string; } — only the listed fields
type UserWithoutEmail = Omit<User, "email">;
// { id: number; name: string; } — everything except the listed fieldsPick<T, K> builds a new type containing only the fields named in K (a union of key names). Omit<T, K> does the opposite — every field of T except the ones named in K. Both exist for the same reason: deriving a smaller, related type from a larger one without manually retyping the fields that should carry over, keeping the derived type automatically in sync if User itself changes later.
Record<K, V>: a dictionary type, expressed generically
type UsersById = Record<number, User>;
// equivalent to: { [key: number]: User }
const cache: UsersById = {
1: { id: 1, name: "Ada", email: "ada@example.com" },
};
type Permissions = Record<"read" | "write" | "delete", boolean>;
// { read: boolean; write: boolean; delete: boolean; } — every literal gets a keyRecord<K, V> describes an object where every key is of type K and every value is of type V — the direct TypeScript way to express "a dictionary/map keyed by X, holding Y." When K is a union of specific string literals (like Permissions above) rather than a general string, Record requires every one of those exact keys to be present — a genuinely useful, stricter guarantee a plain index signature ({ [key: string]: boolean }) doesn't give you at all.
Required<T> and Readonly<T>: the opposite of Partial, and immutability
interface Config {
debug?: boolean;
}
type FullConfig = Required<Config>;
// { debug: boolean; } — the ? is removed, debug is now mandatory
type FrozenUser = Readonly<User>;
const u: FrozenUser = { id: 1, name: "Ada", email: "ada@example.com" };
u.name = "New Name"; // Error: Cannot assign to 'name' because it is a read-only propertyRequired<T> strips optionality from every field, the exact mirror image of Partial<T> — useful once a type with some optional fields (often config-like types with sensible defaults) needs to be fully validated/resolved before use. Readonly<T> marks every field readonly, which — like every TypeScript modifier — is a compile-time-only guarantee (nothing stops a readonly field from being mutated via Object.assign or by casting past the type system), but it does catch the common, accidental "I forgot this shouldn't be mutated" mistake directly in the editor.
ReturnType<T> and Parameters<T>: deriving a type from a function, instead of the other way around
function createUser(name: string, email: string) {
return { id: Date.now(), name, email };
}
type NewUser = ReturnType<typeof createUser>;
// { id: number; name: string; email: string; } — derived from the function's actual return
type CreateUserArgs = Parameters<typeof createUser>;
// [name: string, email: string] — a tuple of the function's parameter typesEvery utility type so far transforms an existing type. ReturnType<typeof fn> and Parameters<typeof fn> go the other direction: they extract a type from a function, given the function's own value (typeof createUser turns the function value into its type first, since ReturnType needs a function type, not a function value). This is the standard way to derive a data type from the single source of truth of "what this function actually returns/accepts" — without manually redeclaring a matching interface that has to be kept in sync by hand every time the function's signature changes.
Further reading
Check your understanding
A quick comprehension check — not tracked, not graded, just for you.
1. What does `Partial<User>` produce, given `interface User { id: number; name: string; }`?
2. What's the actual difference between `Pick<User, "id" | "name">` and `Omit<User, "email">`, assuming User has exactly id, name, and email?
3. Why does `Record<"read" | "write" | "delete", boolean>` require ALL THREE keys to be present, unlike a plain index signature `{ [key: string]: boolean }`?