Utility Types and Mapped Types
Use built-in utility types to transform object contracts and understand the mapped-type pattern behind them.
Partial, Required, Pick, and Omit
Built-in utility types transform existing types. Partial<T> makes properties optional; Required<T> makes them required; Pick<T, K> selects a subset of keys; Omit<T, K> removes selected keys. These are especially useful for create/update DTOs and separating public data from internal fields.
Utility types do not alter runtime objects. A Partial<User> is a compile-time description, not a function that removes or changes properties. Validate business rules separately: a type may permit an optional field while the domain still requires at least one field in a particular operation.
type User = {
id: string;
name: string;
email: string;
createdAt: Date;
};
type CreateUserInput = Pick<User, "name" | "email">;
type UpdateUserInput = Partial<CreateUserInput>;
type PublicUser = Omit<User, "createdAt">;Pick selects fields for creation, Partial allows updates to include only changed fields, and Omit removes createdAt from the public type.- Utility types transform types, not runtime data.
- Use Pick/Omit to define intentional API shapes.
- Partial is useful for patches but may need extra business validation.
What does `Partial<T>` do?
Readonly, Record, Extract, and Exclude
Readonly<T> makes properties readonly at the type level. Record<K, V> constructs a key/value object. Extract<T, U> keeps union members assignable to U, while Exclude<T, U> removes them. NonNullable<T> removes null and undefined; ReturnType<F> obtains a function's return type.
These tools become easier when you think in terms of unions and transformations. They do not inspect runtime values. For complex public types, consider naming intermediate types so errors and editor tooltips remain understandable.
type Status = "queued" | "running" | "done" | "failed";
type FinishedStatus = Extract<Status, "done" | "failed">;
type ActiveStatus = Exclude<Status, "done" | "failed">;
type MaybeUser = { id: string } | null | undefined;
type UserOnly = NonNullable<MaybeUser>;Extract selects the matching union members; Exclude removes matching members; NonNullable removes nullish members.- Extract filters a union by assignability.
- Exclude removes union members assignable to a target.
- NonNullable removes null and undefined from a type.
What is `Exclude<"a" | "b" | "c", "b">`?
Mapped Types and Key Remapping
Mapped types iterate over keys in a type and produce a new type. The basic pattern is { [K in keyof T]: ... }. You can add or remove modifiers and, in modern TypeScript, remap keys with as. Mapped types power many utility types and can model transformations such as making every field nullable or generating event names.
Use mapped types when the transformation is systematic. For a simple domain contract, an explicitly written type may be easier to maintain. Complex recursive or conditional mapped types can create slow type checking and difficult error messages.
type Getters<T> = {
[K in keyof T as `get${Capitalize<string & K>}`]: () => T[K]
};
type User = { name: string; age: number };
type UserGetters = Getters<User>;
// { getName: () => string; getAge: () => number }T, remaps each key using a template literal type, and returns a function producing the original property type.- Mapped types transform keys systematically.
- Key remapping uses
asin a mapped type. - Keep advanced type transformations readable and purposeful.
Which syntax iterates over the keys of T in a mapped type?