Type Utilities & Inference
๐ง Type Utilities & Inference
๐ Built-In Utility Types
TypeScript provides a standard library of utility types for common transformations:
interface User { id: number; name: string; email: string; age: number; } // Partialโ all properties optional type PartialUser = Partial<User>; // { id?: number; name?: string; email?: string; age?: number; } // Requiredโ all properties required type RequiredUser = Required<PartialUser>; // { id: number; name: string; email: string; age: number; } // Readonlyโ all properties readonly type ReadOnlyUser = Readonly<User>; // { readonly id: number; readonly name: string; ... } // Pickโ select specific properties type UserPreview = Pick<User, "name" | "email">; // { name: string; email: string; } // Omitโ exclude specific properties type UserWithoutPassword = Omit<User, "password">; // { id: number; name: string; email: string; age: number; } // Recordโ build object with specific keys type UserRoles = Record<"admin" | "user" | "guest", User[]>; // { admin: User[]; user: User[]; guest: User[]; }
Partial & Required
Toggle optionality. Partial<T> makes all optional; Required<T> makes all required.
Readonly
Make all properties immutable. Can't reassign after creation.
Pick & Omit
Pick selects properties; Omit excludes them. Slice a type.
Record
Build an object type from keys. Record<K, V> creates { [key in K]: V }.
๐ Extract & Exclude: Filter Unions
Work with unions by keeping or removing types:
type StringOrNumber = string | number | boolean; // Extractโ keep types assignable to U type Strings = Extract<StringOrNumber, string>; // string // Excludeโ remove types assignable to U type NonStrings = Exclude<StringOrNumber, string>; // number | boolean // Practical: filter event types type Events = "click" | "focus" | "blur" | "change"; type FocusEvents = Extract<Events, "focus" | "blur">; // "focus" | "blur"
๐ Extract Types From Functions
Infer function signatures to build related types:
function getUser(id: number): { id: number; name: string } { return { id, name: "Alice" }; } // ReturnTypeโ extract return type type UserReturn = ReturnType<typeof getUser>; // { id: number; name: string } // Parametersโ extract parameter types type GetUserParams = Parameters<typeof getUser>; // [id: number] // ConstructorParametersโ for class constructors class User { constructor(name: string, age: number) { } } type UserConstructorParams = ConstructorParameters<typeof User>; // [name: string, age: number]
๐ฏ Type Predicates: Smart Narrowing
Custom type guards that narrow types intelligently:
interface Dog { bark(): void; } interface Cat { meow(): void; } // Type predicate: "value is Dog" tells TypeScript to narrow function isDog(animal: Dog | Cat): animal is Dog { return typeof (animal as Dog).bark === "function"; } const pet: Dog | Cat = getPet(); if (isDog(pet)) { pet.bark(); // โ TypeScript knows pet is Dog } else { pet.meow(); // โ TypeScript knows pet is Cat }
๐ Composing Utility Types
Chain utilities to build complex transformations:
interface User { id: number; name: string; email: string; password: string; } // Exclude password, make all optional, make readonly type SafeUserPreview = Readonly<Partial<Omit<User, "password">>>; // Equivalent to: // { // readonly id?: number; // readonly name?: string; // readonly email?: string; // } // Use it for API responses const preview: SafeUserPreview = { name: "Alice", // email is optional, id is optional };
๐๏ธ Real-World Pattern: Generic API Wrapper
Build Type-Safe CRUD Operations
interface Entity { id: number; createdAt: Date; updatedAt: Date; } interface ApiResponse<T extends Entity> { status: "success" | "error"; data?: T; error?: string; } class Repository<T extends Entity> { // Create uses partial (no id, timestamps auto-generated) async create(data: Omit<T, "id" | "createdAt" | "updatedAt">): Promise<T> { // Simulate API call return { ...data, id: Math.random(), createdAt: new Date(), updatedAt: new Date() } as T; } // Update uses partial (update only what changed) async update(id: number, data: Partial<Omit<T, "id" | "createdAt">>): Promise<T> { // Update logic here return {} as T; } } // Usage with User type interface User extends Entity { name: string; email: string; } const userRepo = new Repository<User>(); // Create requires name and email, NOT id/timestamps await userRepo.create({ name: "Alice", email: "alice@example.com" }); // Update can be partial await userRepo.update(1, { name: "Bob" }); // โ Only name
๐ป Coding Challenges
Challenge 1: Build Custom Utilities
Create utility types: GetType<T, K> (property type), Flatten<T> (array), Nullable<T> (add null to all properties).
Goal: Build reusable utility types using conditional, mapped, and utility type composition.
Challenge 2: Type Predicates
Write custom type guard functions: isString(), isArray<T>(), isRecord<T>(). Test them with discriminated unions.
Goal: Master type predicates and smart narrowing.
Challenge 3: Compose Utilities
Use Pick, Omit, Partial, Readonly together to build derived types. Create safe API response types from your domain types.
Goal: Combine utilities to solve real-world problems.
Most built-in utilities (Partial, Readonly, etc.) work only at the top level. If you need to make nested properties optional, you'll build recursive utilities. That's advanced, but it's possible using conditional types and mapped types.
๐ฏ What's Next
With type utilities mastered, we'll explore Error Handling & Validation โ custom error types, runtime validation, and exhaustiveness checking for bullet-proof code.