TypeScript

TypeScript Utility Types👨‍💻

Utility types are built-in type transformations that ship with TypeScript. They handle the patterns you'd otherwise write by hand — making properties optional for PATCH endpoints, stripping sensitive fields from API responses, extracting return types from functions you don't control. They're not magic: every one of them is implemented with mapped types and conditional types under the hood, which means you can read their source and build your own when the built-ins don't fit.

Key Takeaways

  • 1`Partial<T>` and `Required<T>` toggle optionality on all properties — `Partial` is the standard pattern for update/PATCH payloads where any subset of fields can change
  • 2`Pick<T, K>` and `Omit<T, K>` reshape object types — use `Pick` when you want an allowlist, `Omit` when a blocklist is shorter. Prefer `Omit` for stripping internal fields like passwords before sending a response
  • 3`Record<K, V>` creates typed lookup tables. Pair it with a string literal union as the key type and TypeScript enforces that every key has an entry — no missing cases
  • 4`Exclude<T, U>` and `Extract<T, U>` filter union members (not object properties). `Extract` is particularly powerful with discriminated unions to pull out a single variant by its discriminant
  • 5`ReturnType<T>` and `Parameters<T>` extract signatures from functions you may not own — essential for typing wrappers, mocks, and middleware
  • 6`Awaited<T>` (TS 4.5+) recursively unwraps Promises, and `NoInfer<T>` (TS 5.4+) controls which function parameter drives type inference

Master typescript utility types

Take the TypeScript Essentials course with hands-on lessons and challenges.

Examples

Partial for PATCH endpoints — update only what changed

typescript

Partial<Omit<Product, "id">> is the go-to pattern for PATCH operations. Omit removes the immutable id first, then Partial makes everything else optional. The caller sends only what changed, and TypeScript still validates that the field names and types are correct.

Pick and Omit — shaping API response types from a single source

typescript

Define one source-of-truth type (usually matching your database row), then derive every API shape from it. Omit strips sensitive fields; Pick selects just what a specific view needs. When the base type changes, all derived types update automatically.

Record with literal unions — exhaustive lookup tables

typescript

When the key type is a finite union, Record forces you to handle every member. Add a new status code to HttpStatus and TypeScript immediately flags the missing entry in STATUS_MAP. This is safer than a Map or plain object with string keys.

Extract and Exclude — filtering discriminated unions

typescript

Extract narrows a discriminated union to specific variants using template literal patterns. Each event handler gets exactly the fields that exist on its event type — no type assertions, no runtime checks. This pattern scales well in event-driven architectures.

ReturnType and Parameters — typing wrappers around functions you don't own

typescript

ReturnType<typeof fn> and Parameters<typeof fn> let you build wrappers that stay in sync with the original function. If the SDK updates its signature, your wrapper gets a type error instead of a silent runtime bug. Awaited unwraps the Promise so you get the resolved value type.

NoInfer — controlling which parameter drives inference

typescript

NoInfer blocks a parameter from contributing to type inference. TypeScript infers T from the first argument only, then checks the second against that result. Without it, TypeScript would widen T to include the default value, defeating the constraint. Use this whenever a "default" or "initial" parameter should match values defined elsewhere.

Common Mistakes

Mistake:

Assuming `Readonly<T>` freezes nested objects — then mutating a nested array or object and wondering why TypeScript didn't catch it

Fix:

`Readonly` is shallow. `Readonly<{ items: string[] }>` prevents reassigning `items` but not calling `items.push()`. For deep immutability, use a recursive `DeepReadonly` type or a library like Immer. In most codebases, shallow `Readonly` on API response types is good enough — deep freezing adds complexity without much practical benefit.

Mistake:

Confusing `Omit`/`Pick` (object properties) with `Exclude`/`Extract` (union members) — trying to use `Exclude` to remove a property from an object type

Fix:

`Pick` and `Omit` operate on object property keys. `Extract` and `Exclude` filter members of a union type. To remove a property: `Omit<User, "password">`. To remove a union member: `Exclude<"a" | "b" | "c", "a">`. They look similar but work on fundamentally different things.

Mistake:

Passing an instance type to `ReturnType` — writing `ReturnType<MyClass>` instead of `ReturnType<typeof myFunction>`

Fix:

`ReturnType` expects a function type, not an instance type. For functions: `ReturnType<typeof myFunction>`. For class methods: `ReturnType<MyClass["methodName"]>`. The `typeof` is almost always needed because you're referencing a value, not a type.

Mistake:

Using `Omit` with key names that don't exist on the type — `Omit<User, "pasword">` compiles silently with the typo and does nothing

Fix:

TypeScript doesn't warn when you `Omit` a non-existent key. If you need safety, create a strict version: `type StrictOmit<T, K extends keyof T> = Omit<T, K>`. This catches typos because K is constrained to actual keys of T. Some teams add this to their shared utility types.

Best Practices

  • Define one source-of-truth type per entity (matching your database or domain model), then derive all API shapes with Pick, Omit, and Partial — never duplicate field definitions across request and response types
  • Compose utility types instead of nesting deeply: `Readonly<Pick<User, "id" | "name">>` reads left to right. If a composition gets complex, extract it into a named type alias with a descriptive name
  • Use `Record<LiteralUnion, V>` instead of `{ [key: string]: V }` when the keys are known at compile time — it gives you exhaustiveness checking and autocomplete for free
  • Prefer `Omit` over `Pick` when stripping a small number of fields from a large type — it's more maintainable because adding a new field to the base type automatically includes it in the derived type
  • Reach for `ReturnType<typeof fn>` and `Parameters<typeof fn>` when wrapping third-party functions — it keeps your wrapper in sync with the original without importing internal types you weren't meant to depend on
  • Use `NoInfer` sparingly and intentionally — it solves a specific problem (inference coming from the wrong parameter). If you don't have that problem, adding it just confuses readers

Summary

TypeScript's utility types are composable building blocks for type transformations. Partial and Required toggle optionality. Pick and Omit reshape objects. Record builds typed dictionaries. Exclude and Extract filter unions. ReturnType and Parameters extract function signatures. Awaited unwraps Promises. NoInfer controls inference direction. The key skill is not memorizing each one — it's knowing when to compose them. Define your base types once, then derive everything else.

Practice TypeScript with hands-on challenges

Learn typescript utility types hands-on in your IDE

Interactive lessons and challenges on Stanza, practice in VS Code, Cursor, or the web.

Related Concepts

Related Cheatsheets

Master TypeScript with Stanza

Interactive lessons and challenges, right in your code editor.

Check the free courses. No credit card.