Types in Motion
Utility types
Lesson 3 of 3
What you'll learn
- Transform a type with
Partial,Required,Pick, andOmit - 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.
Run it. The map keys are the roles; the values are their permissions.
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.