A teammate asked me last quarter to count the utility types I had used in the previous PR. The answer was nine. Nine utility types in one PR, none of them remarkable, all of them pulling their weight: Pick, Omit, Partial, ReturnType, Awaited, Record, NonNullable, Parameters, and Readonly. The reason it stuck with me is that five years earlier, the same PR would have had any in three places and a hand-rolled interface for every transformation. The utility types were not novel; they had been there for years. The shift was that I had finally learned which ones to reach for in which situations.
The argument I want to make is that utility types are not a separate API to memorise; they are the working set of a typed codebase. Once you can name the seven or eight that show up most often, the rest fall out as compositions. This piece is the rotation I cycle through every week, with the actual cases that put each one in my hands.
The seven I use weekly
Listed in roughly the order they appear in the PRs I review.
| Utility | What it does | When I reach for it |
|---|---|---|
Partial<T> | Makes every property optional | Update payloads, "patch" methods, options merging |
Required<T> | Makes every property required | Re-narrowing after a Partial round trip |
Pick<T, K> | Keep only the listed keys | Public-facing DTOs derived from internal types |
Omit<T, K> | Remove the listed keys | Strip server-controlled fields from create payloads |
Record<K, V> | Map type with K keys, V values | Lookup tables, dictionaries by union of literal keys |
Readonly<T> | Mark all properties readonly | Guard frozen state, redux-style stores |
ReturnType<F> | The return type of a function | Inferring action payloads, hook results, factory outputs |
These seven cover, by my rough count, 80 percent of the utility-type uses in the codebases I work in. The remaining 20 percent are the ones I use less often but hit the right note when they appear (Awaited, Parameters, NonNullable, Exclude, Extract).
Partial and Required: the optional-property pair
Partial<T> rebuilds T with every property marked optional. The classic application is "update" payloads: a function that lets you update any subset of fields on an entity.
The pair to know is Required<T>, which forces every property to be present. The combination shows up in form-handling code: a draft starts as Partial<User>, and the validator turns it into Required<User> only after every field has been confirmed non-null.
The cast at the end is honest. The validator did the runtime work; the type cast is the compiler's confirmation that the runtime check was sufficient. This pattern is the cleanest way I have found to bridge "untrusted input" to "validated, fully-populated record" without writing a parallel type for the validated form.
Pick and Omit: subset and complement
Pick<T, K> keeps only the named keys; Omit<T, K> drops them. They are the same operation expressed two ways. I reach for whichever makes the call site shorter.
The composition Omit<Partial<T>, K> is something I write multiple times a week. "All optional, but the server owns these specific keys" is the shape of nearly every PATCH endpoint I have ever written.
The mistake I see in code review is hand-rolling these subsets:
Two parallel interfaces, drift waiting to happen. Add avatarUrl to User and you have to remember to add it to PublicUser, and you only catch the mismatch at runtime if your tests are thorough. The Pick derivation makes the relationship explicit at the type level.
Record: the dictionary type
Record<K, V> is the type of an object with keys of type K and values of type V. The killer use is mapping a union of string literals to a value type:
If you forget the home key, the compiler tells you. If you add a key that is not in the union, the compiler tells you. The exhaustiveness check is automatic.
Record<string, V> is the looser dictionary form: any string key, with all values of type V. I treat this as a code smell when I see it. If the keys really are arbitrary strings, the type is honest; if the keys are actually a known union, switch to the literal form so the compiler can help you.
Readonly: the immutability marker
Readonly<T> marks every property as readonly at compile time. The runtime is unaffected; the marker is purely a check that the compiler enforces.
The pattern I use in shared state stores: declare the public type as Readonly<T>, declare the internal mutable type as plain T, and only cross the boundary in the store's privileged update functions. Consumers cannot mutate the state by accident; the compiler refuses.
ReadonlyArray<T> and ReadonlyMap<K, V> are the array and map equivalents. Same idea: the immutable view is a different type from the mutable view, even though the runtime object is identical.
ReturnType and Parameters: introspecting functions
ReturnType<F> extracts the return type of F. Parameters<F> extracts the parameter list as a tuple type. Both are invaluable when you need to derive types from existing function signatures rather than restate them.
The pattern shows up in test fixtures, in Redux reducer types (where the action type is derived from the action creator's return type), and in higher-order-function compositions where the inner function's signature drives the outer one.
The Parameters<F> here is what makes the wrapper preserve the original signature without restating it.
Awaited: unwrapping promise types
Async functions return Promise<T>. Sometimes you need the T without the wrapper: a callback type that consumes whatever the async function produced, a derived type that mirrors the resolved shape. Awaited<P> is the utility:
Awaited recursively unwraps nested promises, which matters because a function that returns Promise<Promise<T>> (rare but possible) should still surface as T at the consumer's level. The recursive behavior is documented; you do not have to chain Awaited manually.
NonNullable, Exclude, Extract: union surgery
These three operate on union types. NonNullable<T> removes null and undefined from a union. Exclude<T, U> removes anything assignable to U from T. Extract<T, U> keeps only the parts of T assignable to U.
In API code, these three appear constantly. A handler that has narrowed away the null case wants its parameter typed as NonNullable<Status>. A switch over a smaller subset of the union wants Extract. A function that handles "all but the pending case" wants Exclude.
What I avoid: abusing utility types as logic
Utility types compose, and the composition can get clever. I have seen single type aliases that are a five-deep nest of Pick, Omit, Partial, Record, and conditional types, and the result is unreadable even with the IDE's hover preview.
The check I run on my own code: if the type alias takes more than two seconds to read aloud, it should be split into named intermediate types. The compiler does not care about the difference between one nested alias and three named ones; humans do.
Same type, three names. The PR reviewer who reads the second version six months from now will appreciate the breadcrumbs.
The rotation, condensed
Every week, I cycle through roughly this set of moves:
- Derive shapes from sources of truth.
Pick,Omit,Partial,Requiredinstead of parallel interfaces. - Map literal-key unions to values.
Record<'a' | 'b' | 'c', V>instead of an interface that has to be kept in sync with the union. - Read function and promise types from existing values.
ReturnType<typeof fn>,Parameters<typeof fn>,Awaited<...>. - Surge unions with
NonNullable,Exclude,Extractwhen narrowing past null or partitioning the cases. - Mark immutability with
Readonlyat the boundaries that should not be mutated.
Five moves, seven utilities at the core, three more for surgery. The whole working set fits on a single sticky note. The win is consistency: every codebase that uses these types in the same way is a codebase you can read at a glance. The lose is when teams hand-roll the same shapes and produce drifted parallel interfaces nobody trusts.
The rotation has not changed much in five years. The utilities were already good. The change was learning which ones to reach for first, and that is the kind of muscle memory that pays back every Monday.
