// LOADING
// LOADING
// LOADING_ARTICLE
TypeScript ships with a set of generic utility types that transform existing types rather than define new ones. They save a lot of boilerplate and, more importantly, keep derivative types in sync with their source automatically. Knowing them well is one of the clearest separators between someone who writes TypeScript and someone who uses TypeScript.
This post walks through the most useful ones with examples grounded in everyday API and component work. For the conceptual foundation—why the type system is worth investing in—read Why Type Safety Is Becoming Essential first.
Partial<T> and Required<T>Partial makes every property optional. It is the right tool for update payloads and form draft state, where not every field needs to be present.
typescriptinterface User { id: string; name: string; email: string; bio: string; } // PATCH /api/users/:id — only send changed fields async function updateUser(id: string, patch: Partial<User>): Promise<User> { const res = await fetch(`/api/users/${id}`, { method: 'PATCH', body: JSON.stringify(patch), }); return res.json(); }
Required<T> is the inverse: every optional property becomes mandatory. Useful for asserting that a fully-hydrated record has all fields after a database read.
Pick<T, K> and Omit<T, K>These two are complementary. Pick selects a subset of properties; Omit excludes a subset.
typescripttype UserPreview = Pick<User, 'id' | 'name'>; // { id: string; name: string } type UserWithoutId = Omit<User, 'id'>; // { name: string; email: string; bio: string }
A common pattern: define a full database type, then derive the API request type with Omit<DBModel, 'id' | 'createdAt' | 'updatedAt'>. When you add a field to DBModel, the request type stays correct automatically.
This connects directly to the patterns in Typing API Responses End to End—sharing a single source-of-truth type across the stack is much easier when you can derive variants rather than duplicate definitions.
Record<K, V>Record constructs an object type with keys of type K and values of type V. It is cleaner than { [key: string]: V } when you know the key set.
typescripttype Status = 'active' | 'inactive' | 'pending'; type StatusConfig = Record<Status, { label: string; color: string }>; const STATUS_CONFIG: StatusConfig = { active: { label: 'Active', color: 'green' }, inactive: { label: 'Inactive', color: 'gray' }, pending: { label: 'Pending', color: 'yellow' }, };
Because K is constrained to Status, TypeScript will error if you add a new status variant and forget to add a config entry. That is exhaustiveness checking without extra syntax.
Readonly<T> and ReadonlyArray<T>Readonly prevents property reassignment. It is lightweight immutability—useful for configuration objects and props you want to treat as frozen.
typescriptfunction processConfig(config: Readonly<AppConfig>): void { // config.apiUrl = '...' — TypeScript error }
Note that Readonly is shallow: nested objects remain mutable. For deep immutability you need a recursive utility or a library like ts-essentials.
Exclude<T, U> and Extract<T, U>These work on union types rather than object types.
Exclude<T, U> removes from T any members assignable to U.Extract<T, U> keeps only members of T assignable to U.typescripttype EventType = 'click' | 'focus' | 'blur' | 'submit' | 'keydown'; type KeyboardEvent = Extract<EventType, 'keydown'>; // 'keydown' type NonKeyboardEvent = Exclude<EventType, 'keydown'>; // 'click' | 'focus' | 'blur' | 'submit'
These become especially powerful with discriminated unions. If you model loading state as a union—'idle' | 'loading' | 'success' | 'error'—you can Extract the error states to derive a type for an error-handling component's props without duplicating the literal. See Discriminated Unions: TypeScript's Best Feature for more on that pattern.
NonNullable<T>Strips null and undefined from a type. Useful after null checks that TypeScript cannot narrow automatically:
typescripttype MaybeUser = User | null | undefined; type DefiniteUser = NonNullable<MaybeUser>; // User
ReturnType<T> and Parameters<T>These introspect function types—useful for extracting types from functions you do not own.
typescriptasync function getUser(id: string) { return db.users.findById(id); } type GetUserReturn = Awaited<ReturnType<typeof getUser>>; // The resolved User type, without importing it separately type GetUserArgs = Parameters<typeof getUser>; // [id: string]
Awaited (added in TypeScript 4.5) unwraps Promise chains, so Awaited<ReturnType<...>> gives you the resolved value type of an async function.
Awaited<T>Briefly: Awaited recursively unwraps Promise<T> to T. It is what ReturnType lacked before 4.5 for async functions.
typescripttype A = Awaited<Promise<Promise<string>>>; // string
Once you understand Pick, Exclude, and mapped types, writing your own utilities is straightforward. A common one in form-heavy codebases:
typescript// Make specific keys optional, leave the rest required type PartialBy<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>; type CreateUserPayload = PartialBy<User, 'id' | 'bio'>; // id and bio are optional; name and email are required
For runtime validation of these derived types, Runtime Validation in TypeScript with Zod shows how to bridge compile-time types with actual data coming in from the network. If you are applying these patterns in a Next.js project, How I Structure Large Next.js + Node.js Projects covers where to keep shared type definitions.
| Utility | What it does |
|---|---|
| Partial<T> | All properties optional |
| Required<T> | All properties required |
| Readonly<T> | All properties read-only |
| Pick<T, K> | Keep only keys K |
| Omit<T, K> | Remove keys K |
| Record<K, V> | Object with keys K and values V |
| Exclude<T, U> | Remove union members assignable to U |
| Extract<T, U> | Keep union members assignable to U |
| NonNullable<T> | Remove null and undefined |
| ReturnType<F> | Return type of function F |
| Parameters<F> | Parameter tuple of function F |
| Awaited<T> | Unwrapped Promise value |
These types are not magic—they are implemented in TypeScript's own lib.d.ts using mapped types and conditional types. Looking at their definitions (Ctrl+click in VS Code) is one of the best ways to learn how to write your own. Start reaching for them whenever you find yourself copying a type and changing a few properties—that is almost always a sign that a utility type should be deriving the variant instead.