Skip to main content
Mohammad Shehadeh — home (MSH monogram, letter M filled with the Palestinian flag)

Deep Dive Into TypeScript Utility Types

Published on Last updated on 3 min read
typescript
1// Example of common utility types usage
2interface User {
3  id: string;
4  name: string;
5  email: string;
6  isActive?: boolean;
7}
8
9// Make all properties optional type PartialUser = Partial<User>;
10
11// Make all properties required type RequiredUser = Required<User>;
12
13// Make all properties readonly type ReadonlyUser = Readonly<User>;
14
15// Pick specific properties type UserCredentials = Pick<User, 'email' | 'password'>;

What are Utility Types?

TypeScript ships with built-in utility types that transform existing types. They let you reshape types without rewriting them by hand, which keeps your code type-safe and avoids duplication.

Key Features:

  • Zero runtime overhead - purely for type checking
  • Built into TypeScript - no additional dependencies
  • Composable - combine them for complex transformations
  • Type-safe transformations
  • Reduces type definition duplication

Core Utility Types

Partial<Type>

Makes all properties optional:

typescript
1interface Todo {
2  title: string;
3  description: string;
4}
5
6type PartialTodo = Partial<Todo>; // Equivalent to: // { // title?: string; // description?: string; // }

Required<Type>

Makes all properties required:

typescript
1interface Config {
2  cache?: boolean;
3  timeout?: number;
4}
5
6type RequiredConfig = Required<Config>; // Equivalent to: // { // cache: boolean; // timeout: number; // }

Readonly<Type>

Makes all properties immutable:

typescript
1interface User {
2  id: string;
3  name: string;
4}
5
6type ReadonlyUser = Readonly<User>;
7// Cannot modify properties after creation
8
9const user: ReadonlyUser = { id: '1', name: 'John' };
10// user.name = "Jane";
11// Error!

Record<Keys, Type>

Creates an object type with specific key-value pairs:

typescript
1type CatInfo = {
2  age: number;
3  breed: string;
4};
5
6type CatName = 'miffy' | 'boris' | 'mordred';
7
8const cats: Record<CatName, CatInfo> = {
9  miffy: { age: 10, breed: 'Persian' },
10  boris: { age: 5, breed: 'Maine Coon' },
11  mordred: { age: 16, breed: 'British Shorthair' }
12};

Pick<Type, Keys>

Creates a type by picking specific properties:

typescript
1interface Post {
2  id: string;
3  title: string;
4  content: string;
5  published: boolean;
6}
7
8type PostPreview = Pick<Post, 'id' | 'title'>;
9// Equivalent to:
10// { id: string; title: string; }

Omit<Type, Keys>

Creates a type by excluding specific properties:

typescript
1interface User {
2  id: string;
3  name: string;
4  password: string;
5}
6
7type PublicUser = Omit<User, 'password'>; // Equivalent to: // { // id: string; // name: string; // }

Advanced Utility Types

Exclude<UnionType, ExcludedMembers>

Removes types from a union:

typescript
1type Status = 'pending' | 'active' | 'deleted' | 'banned';
2
3type ActiveStatus = Exclude<Status, 'deleted' | 'banned'>; // Result: "pending" | "active"

NonNullable<Type>

Removes null and undefined from a type:

typescript
1type Response = string | null | undefined;
2
3type ValidResponse = NonNullable<Response>; // Result: string

Parameters<Type>

Extracts parameter types from a function:

typescript
1type FetchUser = (id: string, includeDetails: boolean) => Promise<User>;
2
3type FetchUserParams = Parameters<FetchUser>; // Result: [id: string, includeDetails: boolean]

Common Use Cases

  1. API Response Handling
typescript
1interface APIResponse<T> {
2  data: T;
3  error?: string;
4  status: number;
5}
6
7type SafeResponse<T> = Required<Omit<APIResponse<T>, 'error'>>;
  1. Configuration Objects
typescript
1interface Config {
2  api: string;
3  timeout?: number;
4  retries?: number;
5}
6
7type RequiredConfig = Required<Config>; type ReadonlyConfig = Readonly<RequiredConfig>;
  1. Event Handling
typescript
1type EventHandler = (event: MouseEvent) => void;
2type EventParams = Parameters<EventHandler>;
3type SafeEventHandler = (...args: NonNullable<EventParams>) => void;

Best Practices

  1. Compose Utility Types
typescript
1type SafeReadonly<T> = Readonly<NonNullable<T>>;
  1. Use with Generics
typescript
1function pick<T, K extends keyof T>(obj: T, keys: K[]): Pick<T, K> {
2  // Implementation
3}
  1. Type Inference with Utilities
typescript
1function update<T>(obj: T, updates: Partial<T>): T {
2  return { ...obj, ...updates };
3}

Conclusion

TypeScript's utility types manipulate types while keeping your code type-safe and free of duplication. Learn them well, and they will sharpen your day-to-day TypeScript work.

Quick Recap:

  • Utility types help reduce type definition duplication
  • They provide type-safe transformations at compile time
  • They can be composed to create complex type transformations
  • They work well with generics for reusable type utilities
  • They help maintain type safety in large codebases

Next Steps:

  1. Start using utility types in your existing codebase
  2. Create reusable type utilities for common patterns
  3. Combine utility types with generics for more flexible type definitions
  4. Use them to improve type safety in your API contracts
  5. Explore more advanced combinations for complex type scenarios

References

Related Articles

GET IN TOUCH

Let's work together

I build fast, accessible, and delightful digital experiences for the web. Whether you have a project in mind or just want to connect, I'd love to hear from you.

Get in touch

or reach out directly at hello@mohammadshehadeh.com