Programming

The TypeScript utility types our team actually reaches for

PRG

TypeScript's built-in utility types get less attention than the language's flashier features, generics, conditional types, template literal types, but a handful of them have become genuinely load-bearing in our day-to-day code this year, enough that we ended up documenting them internally as a quick reference for newer team members.

Partial and Pick show up constantly in form state and API payload shaping. A form that edits an existing record rarely needs every field marked required the way the base type does, since a user might only be updating one field at a time, and Partial<FormData> captures that without duplicating the shape of the type by hand. Pick earns its keep whenever a component only needs a narrow slice of a much larger domain type, a summary card that renders three fields from a twenty-field user object shouldn't have to accept the whole object as a prop type just to satisfy the compiler.

Record and the less-loved Extract have quietly saved us from writing custom mapped types more times than we expected this year. Record<Status, string> for mapping an enum-like union to display labels is a pattern that shows up in nearly every project with any kind of status field, and reaching for it instead of a manually typed object with duplicated key names has cut down on a specific class of bug where a new status value gets added to the union but someone forgets to add the corresponding label, since Record enforces that every key is present at compile time rather than leaving it to be discovered at runtime.

Extract and its counterpart Exclude come up less often but solve a real problem when working with discriminated unions, pulling out just the variant of a union that matches a particular condition without writing a type guard from scratch. Omit rounds out the group as the natural complement to Pick, most often for stripping a server-only field like an internal ID out of a type before it's passed down to a component that has no business knowing it exists.

Awaited and ReturnType come up less frequently than the others but solve a specific, recurring annoyance: typing a variable based on what an existing async function actually resolves to, rather than manually re-declaring a type that's already implicitly defined by the function's own implementation. Any time a function's return shape changes, a manually duplicated type silently goes stale until someone notices a mismatch, usually from a confusing compiler error several files away from the actual change; deriving the type instead means it can't drift, because there's nothing to drift.

We also standardized on NonNullable for a specific class of bug that used to slip through fairly often: a value typed as possibly undefined that's actually guaranteed to be defined by the time a particular function receives it, due to validation or filtering that happened upstream in a way the type system has no way of knowing about on its own. Rather than sprinkling non-null assertions through the codebase, which suppress the compiler's checking entirely and provide no long-term protection if that upstream guarantee is ever accidentally removed, NonNullable applied at the boundary where the guarantee is actually established keeps the type accurate for everything downstream of that point.

None of this replaces actually understanding generics or how TypeScript's structural type system works underneath these utilities; the cheat sheet is useful precisely because the team already has that foundation and these are shortcuts for common shapes of problem, not a substitute for understanding what's happening when a mapped type doesn't quite do what someone expected on the first try. We treat the internal reference doc as a starting point for a conversation in review, not a rule to apply without checking whether it actually fits the specific case at hand.

None of these are exotic. What's notable is how much custom type-juggling code they've quietly replaced once the team got comfortable reaching for them by default instead of writing a bespoke mapped type each time the same shape of problem came up.

← Back to the journal

Have a project in mind?
Let’s talk.

Tell us where you are and where you want to go. We'll map the fastest route between the two.

Currently accepting new clients