TypeScript ships a standard library of utility types — generic type transformers that derive new types from existing ones. Instead of hand-writing near-duplicate interfaces (and letting them drift apart), you keep one source of truth and derive the variations. This lesson tours the utilities you will use constantly.
Throughout, we will transform this base type:
interface User {
id: number;
name: string;
email: string;
role: "admin" | "member";
}Partial<T> makes every property optional — perfect for update payloads:
function updateUser(id: number, changes: Partial<User>) {
// merge changes into the stored user
}
updateUser(1, { name: "Ada King" }); // any subset of User is validRequired<T> is the inverse: it strips ? from every property, guaranteeing a fully populated object.
Pick<T, K> keeps only the listed keys; Omit<T, K> removes them:
type UserPreview = Pick<User, "id" | "name">;
// { id: number; name: string }
type NewUser = Omit<User, "id">;
// everything except id — ideal for a create form,
// where the database assigns the idUse Pick when the subset is small and stable; use Omit when you want "everything except". Because both derive from User, adding a field to User automatically flows into the derived types.
Record<K, V> builds an object type with keys K and values V:
type Role = "admin" | "member" | "guest";
const permissions: Record<Role, string[]> = {
admin: ["read", "write", "delete"],
member: ["read", "write"],
guest: ["read"],
};When the key type is a literal union, the compiler enforces completeness: forget the guest entry and you get an error. Record<string, T> is the loose dictionary form.
Readonly<T> marks every property readonly — useful for configuration objects and immutable state:
const config: Readonly<{ apiUrl: string; retries: number }> = {
apiUrl: "https://api.example.com",
retries: 3,
};
config.retries = 5; // ErrorNote it is shallow: nested objects remain mutable unless they are also wrapped.
These extract types from functions, which is invaluable when a library does not export its internal types:
function createSession(userId: number, ttlSeconds: number) {
return { userId, expiresAt: Date.now() + ttlSeconds * 1000 };
}
type Session = ReturnType<typeof createSession>;
// { userId: number; expiresAt: number }
type CreateArgs = Parameters<typeof createSession>;
// [userId: number, ttlSeconds: number]
type Resolved = Awaited<Promise<string>>; // stringtype Status = "active" | "banned" | "deleted";
type Visible = Exclude<Status, "deleted">; // "active" | "banned"
type OnlyBanned = Extract<Status, "banned">; // "banned"
type Definitely = NonNullable<string | null | undefined>; // stringExclude filters members out of a union; Extract keeps only matching members; NonNullable drops null and undefined.
Utility types compose like functions. A common real-world example — an editable subset of a user:
type EditableUser = Partial<Omit<User, "id" | "role">>;
// { name?: string; email?: string }Under the hood these are all mapped types and conditional types, features you can use to build your own utilities once the built-ins run out.
Partial/Required toggle optionality; Pick/Omit select keys; Record builds keyed objects with completeness checks.ReturnType, Parameters, and Awaited extract types from functions and promises.Exclude, Extract, and NonNullable filter unions.Partial<Omit<User, "id">> reads exactly like what it means.Next lesson: Type Aliases vs Interfaces — when to use each way of naming a type.