The TypeScript Utility Types Nobody Teaches You (But Should)
Every TypeScript tutorial covers Partial, Required, and Pick. But TypeScript ships with a whole toolkit of utility types that can save you hours of boilerplate — and most developers never learn about them.
After 3 years of daily TypeScript across 4 projects, here are the utility types I actually use.
1. Omit — The Inverse of Pick
You know Pick keeps fields. Omit removes them. This is the one I reach for most.
interface User {
id: string;
name: string;
email: string;
password: string;
role: 'admin' | 'user';
createdAt: Date;
}
// Don't expose sensitive fields
type PublicUser = Omit<User, 'password' | 'role'>;
// Form input (exclude auto-generated fields)
type CreateUserInput = Omit<User, 'id' | 'createdAt'>;
Pro tip: Chain it. Omit<User, 'id' | 'createdAt' | 'password'> is cleaner than building a type from scratch.
2. Record — Type-Safe Dictionaries
Stop using { [key: string]: T }. Record is cleaner and the intent is obvious.
// Bad (but common)
const statusMap: { [key: string]: number } = {
pending: 0,
active: 1,
banned: 2,
};
// Good
type Status = 'pending' | 'active' | 'banned';
const statusMap: Record<Status, number> = {
pending: 0,
active: 1,
banned: 2,
};
If you forget a status value, TypeScript catches it at compile time. No more undefined surprises.
3. ReturnType and Parameters — Reverse-Engineer Function Signatures
Sometimes the type you need is already hiding in a function you didn't write.
// You're using a library and want to type the response
const fetchUser = async (id: string) => {
const res = await api.get(`/users/${id}`);
return res.data;
};
type User = ReturnType<typeof fetchUser>; // Promise<AxiosResponse<UserData>>
// Extract parameters for event handlers
type EventHandler = Parameters<typeof socket.on>; // [event: string, callback: Function]
This is especially powerful with third-party libraries where you don't control the exported types.
4. ConstructorParameters — Type Factory Functions
class Database {
constructor(url: string, options: { poolSize: number }) {}
query(sql: string) { /* ... */ }
}
type DbConfig = ConstructorParameters<typeof Database>;
// [url: string, options: { poolSize: number }]
function createDb(...args: DbConfig) {
return new Database(...args);
}
5. InstanceType — Extract from Class Constructors
class ErrorWithCode extends Error {
constructor(public code: number, message: string) { super(message); }
}
type MyError = InstanceType<typeof ErrorWithCode>;
// Equivalent to: ErrorWithCode
This matters when you're working with generic factory patterns or dependency injection.
6. Uppercase, Lowercase, Capitalize, Uncapitalize — String Literal Types
TypeScript 4.1 added these. They're weirdly useful for API design.
type HttpMethod = 'get' | 'post' | 'put' | 'delete';
// Normalize to uppercase
type UppercaseMethod = Uppercase<HttpMethod>;
// 'GET' | 'POST' | 'PUT' | 'DELETE'
// API route generation
type Route = `/${string}`;
type CapitalizedRoute = Capitalize<Route>;
7. The One I Wish I Knew Earlier: Awaited
TypeScript 4.5 added Awaited. It recursively unwraps Promise types.
type T1 = Awaited<Promise<string>>;
// string
type T2 = Awaited<Promise<Promise<number>>>;
// number
type T3 = Awaited<boolean | Promise<number>>;
// boolean | number
// Real use case:
type UserResponse = Awaited<ReturnType<typeof fetchUser>>;
// The actual data type, not the Promise wrapper
The Pattern I Follow
Here's my mental model for which utility type to reach for:
| Need | Utility Type |
|---|---|
| Remove fields | Omit |
| Keep specific fields | Pick |
| Map keys to values | Record |
| Extract from function |
ReturnType, Parameters
|
| Unwrap Promise | Awaited |
| Make fields optional | Partial |
| Combine types | Intersection &
|
| Choose between types | Union `\ |
Why This Matters
These aren't just shortcuts. They create single sources of truth. When your {% raw %}User interface changes, every derived type updates automatically. No manual sync. No drift.
The developer who writes Omit<User, 'password'> will have fewer bugs than the one who manually recreates the interface and forgets to update it when User gets a new field.
What utility types do you use daily? What's the one you discovered late and wished you'd known from day one?
Share in the comments — let's build the ultimate TypeScript utility type cheat sheet together.
📖 Read more developer insights and tutorials at codcompass.com
Top comments (0)