TypeScript Intermediate
Course 2 · Chapter 7 · Type Utilities & Inference
🔧 Type Utilities & Inference
TypeScript's type system is powerful enough to build reusable type transformations. This chapter covers built-in utility types (Partial, Pick, Omit, Record, etc.), how to compose them, extract types from values, infer return types, and write type predicates. These tools unlock the full potential of the type system.
📚 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;
}
type PartialUser = Partial<User>;
type RequiredUser = Required<PartialUser>;
type ReadOnlyUser = Readonly<User>;
type UserPreview = Pick<User, "name" | "email">;
type UserWithoutPassword = Omit<User, "password">;
type UserRoles = Record<"admin" | "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;
type Strings = Extract<StringOrNumber, string>;
type NonStrings = Exclude<StringOrNumber, string>;
type Events = "click" | "focus" | "blur" | "change";
type FocusEvents = Extract<Events, "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" };
}
type UserReturn = ReturnType<typeof getUser>;
type GetUserParams = Parameters<typeof getUser>;
class User {
constructor(name: string, age: number) { }
}
type UserConstructorParams = ConstructorParameters<typeof User>;
🎯 Type Predicates: Smart Narrowing
Custom type guards that narrow types intelligently:
interface Dog {
bark(): void;
}
interface Cat {
meow(): void;
}
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();
} else {
pet.meow();
}
🔗 Composing Utility Types
Chain utilities to build complex transformations:
interface User {
id: number;
name: string;
email: string;
password: string;
}
type SafeUserPreview = Readonly<Partial<Omit<User, "password">>>;
const preview: SafeUserPreview = {
name: "Alice",
};
🏗️ 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> {
async create(data: Omit<T, "id" | "createdAt" | "updatedAt">): Promise<T> {
return {
...data,
id: Math.random(),
createdAt: new Date(),
updatedAt: new Date()
} as T;
}
async update(id: number, data: Partial<Omit<T, "id" | "createdAt">>): Promise<T> {
return {} as T;
}
}
interface User extends Entity {
name: string;
email: string;
}
const userRepo = new Repository<User>();
await userRepo.create({ name: "Alice", email: "alice@example.com" });
await userRepo.update(1, { name: "Bob" });
💻 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.
→ Solution
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.
→ Solution
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.
→ Solution
⚠️ Gotcha: Deep Transformations
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.