MSH

Deep Dive Into TypeScript Utility Types

Published on
Last updated on
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 comes with a set of built-in utility types that help manipulate and transform existing types. These utilities are particularly useful for type transformations without manually rewriting types, ensuring type safety and reducing code duplication.

Key Features:

  • Zero runtime overhead - purely for type checking
  • Built into TypeScript - no additional dependencies
  • Highly composable - can be combined for complex transformations
  • Type-safe transformations
  • Reduces type definition duplication

Core Utility Types

Partial<Type>

Makes all properties optional:

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:

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:

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:

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:

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:

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:

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:

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

Parameters<Type>

Extracts parameter types from a function:

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
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
1interface Config { 2 api: string; 3 timeout?: number; 4 retries?: number; 5} 6 7type RequiredConfig = Required<Config>; type ReadonlyConfig = Readonly<RequiredConfig>;
  1. Event Handling
1type EventHandler = (event: MouseEvent) => void; 2type EventParams = Parameters<EventHandler>; 3type SafeEventHandler = (...args: NonNullable<EventParams>) => void;

Best Practices

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

Conclusion

TypeScript's utility types are powerful tools for type manipulation that help maintain type safety while reducing code duplication. Understanding and effectively using these utilities can significantly improve your TypeScript development experience.

Key Takeaways:

  • 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

GET IN TOUCH

Let's work together

I build exceptional and accessible digital experiences for the web

WRITE AN EMAIL

or reach out directly at hello@mohammadshehadeh.com