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
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:

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 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