正在加载,请稍候…

TypeScript Utility Types: Partial, Required, Pick, Omit and More

Master TypeScript built-in utility types with practical examples. Learn Partial, Required, Readonly, Pick, Omit, Record, Exclude, Extract, ReturnType and when to use each.

What Are Utility Types?

TypeScript ships with a set of generic utility types that transform existing types into new ones. They eliminate repetitive type declarations and let you derive new types from existing ones without duplication.

All utility types are built into TypeScript — no imports needed.

Partial

Makes all properties of T optional.

interface User {
  id: number;
  name: string;
  email: string;
  role: 'admin' | 'user';
}

type PartialUser = Partial<User>;
// {
//   id?: number;
//   name?: string;
//   email?: string;
//   role?: 'admin' | 'user';
// }

// Common use case: update function that accepts partial data
async function updateUser(id: number, updates: Partial<User>) {
  return await db.users.update({ where: { id }, data: updates });
}

updateUser(1, { name: 'Alice' });           // ✅ only updating name
updateUser(1, { email: 'new@example.com' }); // ✅ only updating email

Required

Opposite of Partial — makes all optional properties required.

interface Config {
  host?: string;
  port?: number;
  database?: string;
}

type RequiredConfig = Required<Config>;
// { host: string; port: number; database: string; }

function connect(config: RequiredConfig) {
  // All fields guaranteed to be present
}

Readonly

Makes all properties non-assignable after initialization.

type ReadonlyUser = Readonly<User>;

const user: ReadonlyUser = { id: 1, name: 'Alice', email: 'a@b.com', role: 'user' };
user.name = 'Bob'; // ❌ TypeScript Error: Cannot assign to 'name' because it is a read-only property

// Deep readonly (Readonly is shallow)
type DeepReadonly<T> = {
  readonly [K in keyof T]: T[K] extends object ? DeepReadonly<T[K]> : T[K];
};

Pick<T, K>

Creates a type with only the specified keys from T.

type UserPreview = Pick<User, 'id' | 'name'>;
// { id: number; name: string; }

// Use case: API response types that only expose safe fields
type PublicUser = Pick<User, 'id' | 'name'>;  // exclude email and role from public API

function getUserProfile(id: number): Promise<PublicUser> {
  return db.users.findOne({ where: { id }, select: ['id', 'name'] });
}

Omit<T, K>

Creates a type without the specified keys — the inverse of Pick.

type UserWithoutId = Omit<User, 'id'>;
// { name: string; email: string; role: 'admin' | 'user'; }

// Use case: create payloads (before the DB assigns an id)
type CreateUserPayload = Omit<User, 'id'>;

async function createUser(payload: CreateUserPayload): Promise<User> {
  return await db.users.create({ data: payload });
}

// Omit multiple keys
type UserFormData = Omit<User, 'id' | 'role'>;

Record<K, V>

Creates an object type with keys K and values V.

// A map from string to number
type ScoreMap = Record<string, number>;
const scores: ScoreMap = { alice: 100, bob: 95, carol: 87 };

// Map from union type to object
type UserRole = 'admin' | 'editor' | 'viewer';

type RolePermissions = Record<UserRole, { canEdit: boolean; canDelete: boolean }>;

const permissions: RolePermissions = {
  admin:  { canEdit: true,  canDelete: true  },
  editor: { canEdit: true,  canDelete: false },
  viewer: { canEdit: false, canDelete: false },
};

// Type-safe lookup
function getPermissions(role: UserRole) {
  return permissions[role]; // TypeScript knows this always has a value
}

Exclude<T, U>

Removes types from T that are assignable to U.

type Status = 'active' | 'inactive' | 'deleted' | 'banned';

type ActiveStatus = Exclude<Status, 'deleted' | 'banned'>;
// 'active' | 'inactive'

// Common with union types
type NonNullable<T> = Exclude<T, null | undefined>;  // This is actually a built-in!

type StringOrNull = string | null | undefined;
type JustString = Exclude<StringOrNull, null | undefined>;  // string

Extract<T, U>

Keeps only types in T that are assignable to U (opposite of Exclude).

type Mixed = string | number | boolean | null;

type JustStrings = Extract<Mixed, string | number>;  // string | number
type Primitives = Extract<Mixed, string | number | boolean>;  // string | number | boolean

ReturnType

Extracts the return type of a function type.

function getUser() {
  return { id: 1, name: 'Alice', email: 'alice@example.com' };
}

type GetUserReturn = ReturnType<typeof getUser>;
// { id: number; name: string; email: string; }

// Works with async functions (returns the Promise<T>)
async function fetchData() {
  return { users: [] as User[], total: 0 };
}

type FetchDataReturn = Awaited<ReturnType<typeof fetchData>>;
// { users: User[], total: number }

Parameters

Extracts function parameter types as a tuple.

function createPost(title: string, content: string, authorId: number) {
  // ...
}

type CreatePostParams = Parameters<typeof createPost>;
// [title: string, content: string, authorId: number]

// Useful for wrapping functions
function loggedCreatePost(...args: Parameters<typeof createPost>) {
  console.log('Creating post:', args[0]);
  return createPost(...args);
}

NonNullable

Removes null and undefined from T.

type MaybeString = string | null | undefined;
type DefinitelyString = NonNullable<MaybeString>;  // string

// Practical use with API responses
interface ApiResponse {
  user: User | null;
}

function processUser(response: ApiResponse) {
  if (response.user === null) return;
  const user: NonNullable<typeof response.user> = response.user;  // User (not null)
}

Combining Utility Types

The real power comes from combining them:

interface Product {
  id: string;
  name: string;
  price: number;
  description: string;
  imageUrl: string;
  createdAt: Date;
  updatedAt: Date;
  ownerId: string;
}

// Type for creating: no id, timestamps, or ownerId (set by server)
type CreateProductInput = Omit<Product, 'id' | 'createdAt' | 'updatedAt' | 'ownerId'>;

// Type for updating: same fields as create, but all optional
type UpdateProductInput = Partial<CreateProductInput>;

// Type for listing: only preview fields
type ProductSummary = Pick<Product, 'id' | 'name' | 'price' | 'imageUrl'>;

// Type for admin view: everything
type AdminProduct = Readonly<Product>;

Frequently Asked Questions

Q: What's the difference between Pick and Omit? Pick whitelists fields you want to keep. Omit blacklists fields you want to remove. Use Pick when you want a small subset; use Omit when you want everything except a few fields.

Q: When should I use Partial vs optional properties? Use optional properties (?) when a field is truly optional in the base type. Use Partial<T> when you need an all-optional version of a type that's normally fully required — like an update payload.

Q: Does Readonly work deeply? No, Readonly<T> is shallow. For deeply immutable types, use a recursive DeepReadonly generic or the as const assertion.

→ Use the JSON Viewer to inspect TypeScript API responses and type structures.