BuildBot

Types in Motion

Utility types

Lesson 3 of 3

What you'll learn

  • Transform a type with Partial, Required, Pick, and Omit
  • Build lookup maps with Record
  • Keep types DRY by deriving instead of duplicating

Once you have one good type, you rarely write the related ones by hand. Utility types derive them for you, so the variants stay in sync when the original changes.

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

type DraftUser = Partial<User>;        // every field optional
type PublicUser = Omit<User, "email">; // drop a field
type UserName = Pick<User, "name">;    // keep only some

If you add a field to User, all three update automatically. That's the whole point: one source of truth.

Record

Record<Keys, Value> builds an object type from a set of keys to a value type — perfect for lookups and config maps:

type Role = "admin" | "member" | "guest";
type Permissions = Record<Role, string[]>;

const perms: Permissions = {
  admin: ["read", "write", "delete"],
  member: ["read", "write"],
  guest: ["read"],
};

Derive, don't duplicate

When two types drift apart, bugs appear. Reach for utility types whenever a new type is "the old one, but with X changed" — let the compiler keep them aligned.

The challenge builds a Record-style permission map and looks roles up in it. Run it and try adding a role.

A Record permission map

Run it. The map keys are the roles; the values are their permissions.

Loading editor…

That's the everyday type-level toolkit. You can now model data precisely and let the compiler catch mistakes before your users do.

Sign in to save your progress across devices.