Type Utilities & Inference

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;
}

// 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.

โ†’ 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.