DevToolBoxGRATIS
Blog

Cheat Sheet TypeScript Utility Types: Partial, Pick, Omit, dan Lainnya

13 menit bacaoleh DevToolBox

Panduan Lengkap TypeScript Utility Types

Tipe utilitas TypeScript adalah tipe generik bawaan yang mengubah tipe yang ada menjadi tipe baru.

Partial, Required, dan Readonly

These three utility types transform the mutability and optionality of every property at once. They are the most commonly used utility types in TypeScript codebases.

interface User {
  id: number;
  name: string;
  email: string;
  age: number;
}

// Partial<T> - makes all properties optional
type UserUpdate = Partial<User>;
// { id?: number; name?: string; email?: string; age?: number }

function updateUser(id: number, data: Partial<User>): User {
  return { ...getCurrentUser(id), ...data };
}
updateUser(1, { name: 'Alice' }); // OK - only update name

// Required<T> - makes all properties required
interface Config {
  host?: string;
  port?: number;
  timeout?: number;
}
type StrictConfig = Required<Config>;
// { host: string; port: number; timeout: number }

// Readonly<T> - makes all properties readonly
type ImmutableUser = Readonly<User>;
const user: ImmutableUser = { id: 1, name: 'Alice', email: 'a@b.com', age: 30 };
// user.name = 'Bob'; // Error: Cannot assign to 'name' because it is read-only

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

Pick, Omit, dan Extract

Pick and Omit let you create new types by selecting or removing properties. Extract and Exclude operate on union types. These are essential for creating DTOs (Data Transfer Objects) and API shapes.

interface Product {
  id: number;
  name: string;
  price: number;
  stock: number;
  category: string;
  description: string;
}

// Pick<T, K> - select only the specified keys
type ProductCard = Pick<Product, 'id' | 'name' | 'price'>;
// { id: number; name: string; price: number }

// Useful for API response shaping
function getProductCard(p: Product): ProductCard {
  return { id: p.id, name: p.name, price: p.price };
}

// Omit<T, K> - remove specified keys
type ProductForm = Omit<Product, 'id' | 'stock'>;
// { name: string; price: number; category: string; description: string }

// Exclude<T, U> - from union type, exclude assignable to U
type Direction = 'north' | 'south' | 'east' | 'west';
type Horizontal = Exclude<Direction, 'north' | 'south'>;
// 'east' | 'west'

// Extract<T, U> - from union type, keep only assignable to U
type Vertical = Extract<Direction, 'north' | 'south'>;
// 'north' | 'south'

// NonNullable<T> - removes null and undefined
type MaybeString = string | null | undefined;
type DefiniteString = NonNullable<MaybeString>;
// string

Record dan Mapped Types

Record creates object types with a fixed set of keys. Mapped types iterate over union types or object keys to transform every property systematically.

// Record<K, V> - create object type with known keys
type Role = 'admin' | 'user' | 'guest';
type Permissions = Record<Role, string[]>;

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

// Record with nested types
type ApiResponse<T> = Record<string, T>;
type UserMap = Record<number, User>;

// Mapped Types - transform every property
type Nullable<T> = { [K in keyof T]: T[K] | null };
type Optional<T> = { [K in keyof T]?: T[K] };
type Stringify<T> = { [K in keyof T]: string };

// Mapped types with modifiers
type Mutable<T> = { -readonly [K in keyof T]: T[K] }; // remove readonly
type Complete<T> = { [K in keyof T]-?: T[K] };         // remove optional

// Remapping keys with 'as'
type Getters<T> = {
  [K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];
};
type UserGetters = Getters<User>;
// { getId: () => number; getName: () => string; ... }

ReturnType dan Utilitas Fungsi

Function utility types let you extract type information from functions and classes without duplicating type definitions. They're especially useful when working with third-party libraries that don't export their types.

// ReturnType<T> - extract return type of a function
function createUser(name: string, email: string) {
  return { id: Math.random(), name, email, createdAt: new Date() };
}
type CreatedUser = ReturnType<typeof createUser>;
// { id: number; name: string; email: string; createdAt: Date }

// Parameters<T> - extract parameter types as tuple
type CreateUserParams = Parameters<typeof createUser>;
// [name: string, email: string]

// ConstructorParameters<T> - for class constructors
class Server {
  constructor(public host: string, public port: number) {}
}
type ServerArgs = ConstructorParameters<typeof Server>;
// [host: string, port: number]

// InstanceType<T> - get instance type from constructor
type ServerInstance = InstanceType<typeof Server>;
// Server

// Awaited<T> - unwrap Promise types
type UserPromise = Promise<User>;
type ResolvedUser = Awaited<UserPromise>;
// User

async function fetchUser(): Promise<User> { /* ... */ return {} as User; }
type FetchResult = Awaited<ReturnType<typeof fetchUser>>;
// User

Tipe Kondisional dan Infer

Conditional types express if-else logic at the type level. The infer keyword lets you capture and name types within conditional expressions, enabling powerful type extraction patterns.

// Conditional Types
type IsArray<T> = T extends any[] ? true : false;
type CheckString = IsArray<string>;  // false
type CheckArray  = IsArray<string[]>; // true

// infer - extract type from generic
type UnpackArray<T> = T extends (infer U)[] ? U : T;
type StringItem = UnpackArray<string[]>; // string
type NumberItem  = UnpackArray<number>;  // number

// Extract Promise value
type UnpackPromise<T> = T extends Promise<infer U> ? U : T;
type PromiseValue = UnpackPromise<Promise<number>>; // number

// Get first element of tuple
type First<T extends any[]> = T extends [infer F, ...any[]] ? F : never;
type Head = First<[string, number, boolean]>; // string

// Distributive conditional types
type Flatten<T> = T extends Array<infer U> ? U : T;
type Mixed = Flatten<string[] | number | boolean[]>;
// string | number | boolean

Menggabungkan Utility Types

The real power of utility types comes from combining them. Here's a real-world example showing how to build a complete API type system from a single source type:

// Real-world example: API layer with combined utility types

interface ApiUser {
  id: number;
  username: string;
  email: string;
  password: string;   // should never leave server
  createdAt: string;
  updatedAt: string;
}

// Safe user for frontend (strip sensitive fields)
type SafeUser = Omit<ApiUser, 'password'>;

// User creation form (no server-generated fields)
type CreateUserDto = Omit<ApiUser, 'id' | 'createdAt' | 'updatedAt'>;

// Patch request - all fields optional except id
type PatchUserDto = Partial<Omit<ApiUser, 'id'>> & Pick<ApiUser, 'id'>;

// Validation errors map
type ValidationErrors<T> = Partial<Record<keyof T, string>>;
type UserErrors = ValidationErrors<CreateUserDto>;
// { username?: string; email?: string; password?: string }

// Generic API response wrapper
type ApiResult<T, E = string> =
  | { success: true; data: T }
  | { success: false; error: E };

type UserResult = ApiResult<SafeUser>;

Pertanyaan yang Sering Diajukan

What is the difference between Partial<T> and Optional<T>?

Partial<T> is a built-in TypeScript utility type that makes all properties of T optional. Optional<T> is not built-in — it's a common name for custom mapped types that do the same thing. You should always prefer the built-in Partial<T> over writing your own.

When should I use Pick vs Omit?

Use Pick when you want to select a small subset of properties from a large type (you know what you WANT). Use Omit when you want most properties but need to exclude a few (you know what you DON'T WANT). For removing 1-2 properties, Omit is cleaner. For selecting 1-2 properties, Pick is cleaner.

What is the difference between Record<string, T> and { [key: string]: T }?

They are nearly identical in behavior, but Record<string, T> is more readable and the idiomatic TypeScript style. Record<string, T> also plays better with other utility types. One subtle difference: Record<never, T> produces an empty object type {}, while the index signature always allows any string key.

How do I create a deep Partial type?

TypeScript's built-in Partial<T> is shallow — it only makes the top level optional. For deep partial, create a recursive utility type: type DeepPartial<T> = { [K in keyof T]?: T[K] extends object ? DeepPartial<T[K]> : T[K] }. Libraries like ts-toolbelt provide this and many more deep utility types.

What are template literal types and how do they relate to mapped types?

Template literal types (introduced in TS 4.1) let you create string types with embedded type expressions: type EventName<T extends string> = `on${Capitalize<T>}`. Combined with mapped types, you can generate entire interfaces of event handlers, getters, setters, etc., from a single source-of-truth type.

Can I use utility types with interfaces and type aliases both?

Yes. TypeScript utility types work with both interface and type alias definitions interchangeably. Partial<MyInterface> and Partial<MyType> both work the same way. The choice between interface and type alias is mostly stylistic, though interfaces support declaration merging while type aliases do not.

Kesimpulan

TypeScript utility types are force multipliers for your type system. Instead of maintaining dozens of similar interfaces that drift out of sync, define one source-of-truth type and derive all variants with Partial, Pick, Omit, and friends. Your codebase becomes easier to maintain and refactor as requirements change.

Start by auditing your codebase for interfaces that are near-duplicates of each other — those are prime candidates for utility type refactoring. Tools like the viadreams.cc JSON formatter and diff tool can help you compare complex type structures visually.

Alat Terkait

𝕏 Twitterin LinkedIn
Apakah ini membantu?

Tetap Update

Dapatkan tips dev mingguan dan tool baru.

Tanpa spam. Berhenti kapan saja.

Coba Alat Terkait

TSJSON to TypeScriptZDJSON to Zod Schema{ }JSON Formatter

Artikel Terkait

TypeScript Generics Dijelaskan: Panduan Praktis dengan Contoh

Kuasai TypeScript generics dari dasar hingga pola lanjutan.

TypeScript vs JavaScript: Kapan dan Bagaimana Mengonversi

Panduan praktis kapan harus mengonversi TypeScript ke JavaScript dan sebaliknya. Strategi migrasi, tooling, dampak ukuran bundle, dan pertimbangan tim.

JSON ke TypeScript: Panduan Lengkap dengan Contoh

Pelajari cara mengonversi data JSON ke interface TypeScript secara otomatis. Mencakup objek bersarang, array, field opsional, dan praktik terbaik.