tezvyn:

TypeScript Utility Types: Don't Reinvent the Type

AI-drafted, machine-checkedSource: typescriptlang.orgintermediate

Utility types are pre-built functions for your types, transforming them without manual effort. Use `Partial<T>` for update functions or `Readonly<T>` for immutable objects.

WHY IT EXISTS To solve the problem of repetitive type logic. Often, you need a slight variation of an existing type—all properties optional, all required, or all read-only. Writing these transformations manually using conditional and mapped types is error-prone and verbose. Utility types provide a standard, reusable library for these common needs.

THE MENTAL MODEL Utility types are functions for your types. Just as a function f(x) takes a value x and returns a new value, a utility type Utility<Type> takes a type Type and returns a new, transformed type. They are globally available tools for type manipulation, letting you express relationships between types without writing boilerplate.

HOW IT WORKS Utility types are implemented using TypeScript's own advanced type features, like mapped types and conditional types. For example, Partial<T> maps over the keys of T and adds a '?' to make each property optional. Required<T> does the opposite, removing the '?'. Readonly<T> adds the readonly modifier to each property. You don't need to know the implementation to use them, just what they do.

WHEN TO USE IT Use them whenever you need a variation of an existing type. Common scenarios include: creating DTOs for API patch endpoints (Partial<T>), ensuring an object is not mutated after creation (Readonly<T>), making sure all optional properties are provided for a specific use case (Required<T>), or correctly typing the resolved value of an async operation (Awaited<T>).

WHEN NOT TO USE IT Don't use a utility type if you truly need a fundamentally different type structure. If a NewUser type has different properties from a User type (not just different modifiers), a utility type isn't the right tool. They are for transformations, not for creating entirely new, unrelated type shapes. Also, avoid over-nesting them, as it can make type errors difficult to debug.

ONE CANONICAL EXAMPLE A common pattern is updating a database record. You have a Todo interface with a required title and description. An update function, however, should accept an object with either a title, a description, or both. Instead of defining a new UpdateTodo interface, you use Partial<Todo>. The function signature becomes: function updateTodo(id: number, fields: Partial<Todo>). This correctly types the function to accept any subset of Todo's properties, making a call like updateTodo(1, { description: "New description" }) valid.

Read the original → typescriptlang.org

Get five bites like this every day.

Tezvyn delivers a daily feed of 60-second tech bites with quizzes to lock in what you learn.