MASTERING TYPESCRIPT UTILITY TYPES
TypeScript ships with a standard library of generic types that transform, filter, and compose other types. Most developers know Partial and Omit. Fewer know Awaited, Parameters, or template literal types — and almost nobody writes custom utilities until they have been burned by the repetition that makes them necessary.
This article covers the built-in utilities worth knowing, then shows you how to build your own for the patterns that recur in real applications.
THE CORE TRANSFORMATION UTILITIES
Partial and Required are the most commonly used pair. Partial makes every property in a type optional, which is perfect for PATCH API endpoints or merge functions where you only want to specify the fields you care about. Required does the opposite, making every property mandatory. Both are shallow — they only affect the top level and do not recurse into nested objects.
Pick and Omit both create new types by selecting or removing named keys. Pick takes only the keys you list, so you can extract a minimal shape like a JWT payload that only needs id, email, and role from a full User type. Omit takes everything except the keys you list, which is ideal for stripping a passwordHash before sending a user object to the client. Both accept a union of key strings, so you can exclude multiple fields in one expression.
Record maps a key type to a value type. Its main advantage over a plain index signature is that when the key type is a union, TypeScript will check that your object is exhaustive — every member of the union must appear as a key. Defining a Record keyed by a union of HTTP status codes and mapping to strings means TypeScript will error if you forget to handle 404 or add a status that was not in your union.
SET OPERATIONS ON TYPES
Exclude and Extract work on union types. Exclude removes union members that are assignable to the second argument, while Extract keeps only those members. This is useful for filtering a large union of event names down to just keyboard events or just pointer events. It is even more powerful on discriminated unions — extracting from a tagged union type by the value of the discriminant field gives you the exact narrowed type for that case.
NonNullable removes null and undefined from a type. The practical use case is stripping nullability from optional fields, for instance when building a mapped type that enforces all values in an object are defined.
FUNCTION AND PROMISE UTILITIES
ReturnType and Parameters extract information from function types without you having to repeat the type definition. ReturnType gives you the return type of a function, and Parameters gives you the parameter types as a tuple. When you wrap a function — say, adding a logging decorator that should preserve the original function's signature — using Parameters and ReturnType instead of writing out the types manually ensures the wrapper always stays in sync with the wrapped function, even if the function's signature changes later.
Awaited recursively unwraps Promise types. Awaited applied to a Promise of string gives you string. Applied to a Promise of a Promise of number gives you number. Applied to a non-Promise type it passes through unchanged. The most valuable use case is inferring the resolved type of async operations automatically: wrapping ReturnType in Awaited gives you the actual data type returned by an async function, without having to write it out separately.
TEMPLATE LITERAL TYPES
Template literal types bring JavaScript's template literal syntax to the type level to generate string union types programmatically. Combining a Direction union of "top", "right", "bottom", "left" with a template produces all four padding-direction and margin-direction combinations as a type. Combining an event name union like "click", "focus", "blur" with Capitalize in a template produces "onClick", "onFocus", "onBlur".
The most powerful application is a strongly typed event emitter. You define an EventMap object type where keys are event names and values are the payload types for each event. An on function generic over the key type receives an event name and a handler, and TypeScript infers the correct payload type for each handler automatically. Passing an event name that is not a key of EventMap is a compile error.
CUSTOM UTILITY TYPES
DeepPartial extends the standard Partial to recurse through nested objects. The standard Partial only makes top-level properties optional, so nested objects remain fully required. DeepPartial uses a conditional type: if T is an object, it maps over keyof T and applies DeepPartial recursively to each value; otherwise it returns T unchanged. This is essential for configuration objects with several levels of nesting, where you want to be able to pass in only the fields you need to override and let everything else fall back to defaults.
Prettify is a zero-cost developer experience utility. When you compose types with intersections — combining Base and WithName and WithEmail — TypeScript's hover tooltip shows the raw intersection rather than the resolved flat shape, which is hard to read. Prettify is a mapped type that iterates over keyof T and returns T[K] for each key, intersected with an empty object. This forces TypeScript to evaluate the intersection, so hover tooltips show the clean resolved shape. It has no runtime impact whatsoever.
StrictOmit fixes a footgun in the built-in Omit. The standard Omit constrains its key parameter to string or number or symbol, not keyof T, which means omitting a key that does not exist on the type compiles without error. StrictOmit simply re-declares Omit with the key constrained to keyof T, so passing a nonexistent key name is a type error caught at compile time.
ValueOf extracts the union of all value types from an object type — the value-space equivalent of keyof. The most useful application is with const objects used as enums. Defining a ROUTES object with as const and then using ValueOf on typeof ROUTES gives you the literal union of all route strings. Functions that accept a route can then be typed to only allow valid route values, with autocomplete and compile-time checking.
PartialBy makes a specific subset of keys optional while leaving the rest required. The built-in utilities cannot express this cleanly on their own, but combining Omit to remove the keys you want to make optional, Partial and Pick to make just those keys optional, and intersecting the two results in the desired shape. For a form input type where most fields are required but publishedAt and tags are optional for drafts, PartialBy expresses that precisely.
KeysOfType filters the keys of a type to only those whose values extend a given type. It uses a mapped type with the -? modifier to remove optionality from the mapping, so optional fields are included in the result. It maps over keyof T and returns K when T[K] extends the filter type, or never otherwise. Indexing the mapped type by keyof T collapses the never values away, leaving only the matching key names. This is useful for building generic utilities that should only operate on certain fields — a function that reads string fields from a form object can declare its key parameter as KeysOfType of string, and TypeScript will only allow string-valued keys to be passed.
MAPPED TYPES
Mapped types transform every property of a type systematically. They are how Partial, Required, Readonly, and most of the built-in utility types are implemented. You can build your own to generate derived types from existing ones.
A validation schema generator is a practical example. You write a generic mapped type that takes a data type T and produces a schema type where every key in T maps to an object with a required boolean and an optional validate function that takes T[K] and returns a string or null. You then define your schema against a specific form type and TypeScript ensures every field has a corresponding entry.
The as clause in mapped types lets you remap the output key names. Mapping over keyof T and producing a template literal key name like getFirstname from firstname creates a type with getter method names derived automatically from the data type's field names.
RULES OF THUMB
Use built-in utilities freely. Partial, Omit, Pick, and Record are not advanced TypeScript — they should appear in everyday code across any reasonably typed codebase.
Write custom utilities when you see the same type transformation repeated three or more times. Extract it into a named generic just as you would extract a repeated function.
Avoid type gymnastics for its own sake. If a type is hard to read, it will be hard to debug. A clear comment or a slightly looser type is often better than a clever recursive conditional that few team members can parse at a glance.
Test your utility types using assertion helpers. A simple Expect generic constrained to true, combined with an Equal generic that checks mutual assignability, lets you write type-level unit tests that will catch regressions when TypeScript versions change behavior.
TypeScript's type system is Turing complete — you can do almost anything with it. The skill is knowing when to stop.
Back to Methods
MethodAdvanced
Mastering TypeScript Utility Types
Deep dive into TypeScript's built-in utility types and how to create your own for better type safety.
February 28, 202418 min read
TypeScriptTypesBest Practices