One of the biggest frustrations I faced when starting with TypeScript was the feeling of repetitive boilerplate. I would find myself creating three different versions of the same User interface—one for creating a user, one for updating a user, and one for the public profile. It felt like I was fighting the type system rather than using it.
That’s where this typescript utility types cheatsheet comes in. TypeScript provides a set of built-in generic types that allow you to transform existing types into new ones without rewriting the properties. If you’re just starting out, I highly recommend checking out my typescript for beginners tutorial to get the basics of types and interfaces down first.
Core Concepts: Why Use Utility Types?
At their heart, utility types are essentially functions for your types. Instead of manually defining a new interface, you pass an existing one into a utility type, and TypeScript calculates the resulting type for you. This ensures a single source of truth; if you add a field to your main User interface, every utility type derived from it updates automatically.
To understand these, you need to be comfortable with how TypeScript handles generics. If you’re feeling shaky on that, my typescript generic constraints guide explains how these flexible type wrappers work under the hood.
Getting Started: The Essential Utility Types
I’ve broken these down by how I actually use them in my production projects. Let’s start with the most common transformations.
1. Making Fields Optional with Partial<T>
Partial<T> takes all properties of a type and makes them optional. This is my go-to for “Update” or “Patch” requests where the client might only send one or two fields to change.
interface User {
id: string;
username: string;
email: string;
age: number;
}
// Instead of creating an 'UpdateUser' interface manually:
type UpdateUserRequest = Partial<User>;
const updateData: UpdateUserRequest = {
email: 'new-email@example.com' // No error, even though id and username are missing
};
2. Extracting Specifics with Pick<T, K>
Sometimes you have a massive object, but a specific function only needs a tiny slice of it. Pick<T, K> allows you to create a new type by choosing only the keys you want.
interface Product {
id: string;
name: string;
price: number;
description: string;
stockCount: number;
}
// I only want the name and price for a product card UI component
type ProductCardProps = Pick<Product, 'name' | 'price'>;
const card: ProductCardProps = {
name: 'Mechanical Keyboard',
price: 120
};
3. Removing Noise with Omit<T, K>
Omit<T, K> is the inverse of Pick. Use it when you want everything except a few specific properties. In my experience, this is incredibly useful for removing sensitive data like passwords before passing an object to a frontend component.
interface UserAccount {
id: string;
username: string;
passwordHash: string;
secretQuestion: string;
}
// Create a safe version of the user for the UI
type UserPublicProfile = Omit<UserAccount, 'passwordHash' | 'secretQuestion'>;
const profile: UserPublicProfile = {
id: 'u123',
username: 'dev_ajmani'
};
As shown in the diagram below, these three utilities—Partial, Pick, and Omit—form the foundation of most type transformations in modern TypeScript apps.
Advanced Transformations for Pro Workflows
Once you’ve mastered the basics, there are a few more specialized tools in the typescript utility types cheatsheet that can save you from any casting.
Readonly<T>
When passing configuration objects or state, you want to ensure nothing accidentally mutates the data. Readonly<T> makes every property immutable.
interface Config {
apiUrl: string;
timeout: number;
}
const settings: Readonly<Config> = {
apiUrl: 'https://api.ajmani.dev',
timeout: 5000
};
// settings.timeout = 3000; // Error: Cannot assign to 'timeout' because it is a read-only property.
Required<T>
This is the opposite of Partial. If you have a type with optional fields but you’ve reached a point in your logic where you know those fields must be present, Required<T> enforces it.
Common Mistakes When Using Utility Types
- Overusing Partial: I’ve seen developers make everything
Partialto avoid errors. This defeats the purpose of TypeScript. Only use it for truly optional inputs. - Deep Nesting: Utility types are shallow. If you have a
Partial<User>and that user has anAddressobject, theAddressproperties are still required. You’ll need a custom “DeepPartial” type for that. - Ignoring the Source: If you find yourself using
Omiton 10 different fields, it’s a sign your base interface is too bloated. Consider splitting it into smaller, more focused interfaces.
Your TypeScript Learning Path
Mastering these utilities is a huge step toward senior-level TypeScript. If you’re looking for what to study next, I recommend this sequence:
- Basic Types & Interfaces (The foundation)
- Utility Types (The transformations)
- Generic Constraints (The flexibility)
- Mapped Types & Conditional Types (The advanced magic)
If you want more tips on building scalable apps, check out my other guides on automation tools and developer productivity to streamline your workflow.
Tools to Enhance Your Type Experience
While the built-in utilities are great, I use a few tools to make them easier to manage:
- TypeScript Playground: Perfect for testing a complex
OmitorPickchain before putting it in your codebase. - TS-Reset: A library that fixes some of the annoying defaults of built-in types (like
Partial). - Zod: While not a utility type, Zod allows you to infer TypeScript types from runtime schemas, which pairs perfectly with utility types.