TypeScript Utility Types

Built-in type transformation helpers

🛠️ What are Utility Types?

Utility types are built-in TypeScript helpers that transform existing types. They save time by providing common type transformations like making properties optional, readonly, or picking specific properties from types.


type User = { name: string; age: number; email: string };
type PartialUser = Partial<User>;  // All properties optional
type UserName = Pick<User, "name">;  // Only name property
                                    

Common Utility Types

Partial<T>

Makes all properties optional

type User = { name: string; age: number };
type PartialUser = Partial<User>;

Required<T>

Makes all properties required

type Optional = { name?: string };
type Required = Required<Optional>;
🔒

Readonly<T>

Makes all properties readonly

type User = { name: string };
type ReadonlyUser = Readonly<User>;
🎯

Pick<T, K>

Select specific properties

type User = { name: string; age: number };
type Name = Pick<User, "name">;

🔹 Partial<T>

Makes all properties optional - useful for updates:

type User = {
  id: number;
  name: string;
  email: string;
  age: number;
};

type PartialUser = Partial<User>;
// Result: {
//   id?: number;
//   name?: string;
//   email?: string;
//   age?: number;
// }

function updateUser(id: number, updates: PartialUser) {
  // Update only provided fields
  console.log(`Updating user ${id}`, updates);
}

updateUser(1, { name: "Alice" });           // ✓ Valid
updateUser(2, { email: "[email protected]" }); // ✓ Valid
updateUser(3, {});                          // ✓ Valid

🔹 Required<T>

Makes all properties required - opposite of Partial:

type OptionalUser = {
  name?: string;
  email?: string;
  age?: number;
};

type RequiredUser = Required<OptionalUser>;
// Result: {
//   name: string;
//   email: string;
//   age: number;
// }

const user1: OptionalUser = { name: "Alice" };  // ✓ Valid
const user2: RequiredUser = { name: "Bob" };    // ✗ Error: missing email, age

const user3: RequiredUser = {
  name: "Charlie",
  email: "[email protected]",
  age: 30
};  // ✓ Valid

🔹 Readonly<T>

Makes all properties immutable:

type Config = {
  apiUrl: string;
  timeout: number;
  retries: number;
};

type ReadonlyConfig = Readonly<Config>;
// Result: {
//   readonly apiUrl: string;
//   readonly timeout: number;
//   readonly retries: number;
// }

const config: ReadonlyConfig = {
  apiUrl: "https://api.example.com",
  timeout: 5000,
  retries: 3
};

console.log(config.apiUrl);  // ✓ Can read
// config.timeout = 10000;   // ✗ Error: Cannot assign to readonly

🔹 Pick<T, K>

Select specific properties from a type:

type User = {
  id: number;
  name: string;
  email: string;
  password: string;
  createdAt: Date;
};

type PublicUser = Pick<User, "id" | "name" | "email">;
// Result: {
//   id: number;
//   name: string;
//   email: string;
// }

const publicProfile: PublicUser = {
  id: 1,
  name: "Alice",
  email: "[email protected]"
  // password not included - good for API responses!
};

🔹 Omit<T, K>

Remove specific properties from a type:

type User = {
  id: number;
  name: string;
  email: string;
  password: string;
};

type UserWithoutPassword = Omit<User, "password">;
// Result: {
//   id: number;
//   name: string;
//   email: string;
// }

type UserWithoutSensitive = Omit<User, "password" | "email">;
// Result: {
//   id: number;
//   name: string;
// }

const safeUser: UserWithoutPassword = {
  id: 1,
  name: "Bob",
  email: "[email protected]"
};

🔹 Record<K, T>

Create an object type with specific keys and value type:

type Role = "admin" | "user" | "guest";

type Permissions = Record<Role, string[]>;
// Result: {
//   admin: string[];
//   user: string[];
//   guest: string[];
// }

const permissions: Permissions = {
  admin: ["read", "write", "delete"],
  user: ["read", "write"],
  guest: ["read"]
};

// Another example
type PageInfo = Record<string, { title: string; views: number }>;

const pages: PageInfo = {
  home: { title: "Home", views: 1000 },
  about: { title: "About", views: 500 }
};

🔹 Exclude<T, U> and Extract<T, U>

Filter union types:

type AllTypes = string | number | boolean | null;

// Exclude: Remove types from union
type NoNull = Exclude<AllTypes, null>;
// Result: string | number | boolean

type OnlyPrimitives = Exclude<AllTypes, null | boolean>;
// Result: string | number

// Extract: Keep only matching types
type OnlyStrings = Extract<AllTypes, string>;
// Result: string

type StringOrNumber = Extract<AllTypes, string | number>;
// Result: string | number

🔹 ReturnType<T> and Parameters<T>

Extract types from functions:

function createUser(name: string, age: number) {
  return { id: 1, name, age, createdAt: new Date() };
}

// Get return type
type User = ReturnType<typeof createUser>;
// Result: { id: number; name: string; age: number; createdAt: Date }

// Get parameter types
type CreateUserParams = Parameters<typeof createUser>;
// Result: [name: string, age: number]

// Use in practice
function logUser(user: User) {
  console.log(user.name, user.age);
}

function callCreateUser(...args: CreateUserParams) {
  return createUser(...args);
}

🔹 NonNullable<T>

Remove null and undefined from a type:

type MaybeString = string | null | undefined;

type DefiniteString = NonNullable<MaybeString>;
// Result: string

type MixedTypes = string | number | null | undefined | boolean;
type NoNulls = NonNullable<MixedTypes>;
// Result: string | number | boolean

function processValue(value: MaybeString): NonNullable<MaybeString> {
  if (value === null || value === undefined) {
    throw new Error("Value cannot be null or undefined");
  }
  return value;  // TypeScript knows it's string here
}

🔹 Practical Example: API Response Handler

Combine utility types for real-world scenarios:

type User = {
  id: number;
  name: string;
  email: string;
  password: string;
  role: "admin" | "user";
  createdAt: Date;
  updatedAt: Date;
};

// For API responses (no password)
type UserResponse = Omit<User, "password">;

// For creating users (no id, dates)
type CreateUserInput = Omit<User, "id" | "createdAt" | "updatedAt">;

// For updating users (all optional except id)
type UpdateUserInput = Partial<Omit<User, "id">> & Pick<User, "id">;

// For public profiles (limited info)
type PublicProfile = Pick<User, "id" | "name">;

// Usage
function createUser(input: CreateUserInput): UserResponse {
  // Create user logic
  return {
    id: 1,
    name: input.name,
    email: input.email,
    role: input.role,
    createdAt: new Date(),
    updatedAt: new Date()
  };
}

function updateUser(input: UpdateUserInput): UserResponse {
  // Update user logic
  return {} as UserResponse;
}

💡 Key Takeaways

  • Partial<T> - Makes all properties optional
  • Required<T> - Makes all properties required
  • Readonly<T> - Makes all properties immutable
  • Pick<T, K> - Selects specific properties
  • Omit<T, K> - Removes specific properties
  • Record<K, T> - Creates object with specific keys
  • Combine utilities for complex type transformations

🧠 Test Your Knowledge

What does Partial<User> do?