DEV Community

kol kol
kol kol

Posted on

The TypeScript Utility Types Nobody Teaches You (But Should)

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'>;
Enter fullscreen mode Exit fullscreen mode

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,
};
Enter fullscreen mode Exit fullscreen mode

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]
Enter fullscreen mode Exit fullscreen mode

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);
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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>;
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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)