AdvancedTypeScript · Lesson 2 of 10

Utility Types & the satisfies Operator

Partial, Pick, Omit, Record, ReturnType, Awaited — and safer config with satisfies.

TypeScript ships utility types that cover most everyday transformations: Partial<T> (all optional — perfect for update payloads), Required<T>, Pick<T, K> and Omit<T, K> (subset of keys), Record<K, V> (dictionary), ReturnType<F>, Parameters<F> and Awaited<T>.

Derive types from a single source of truth instead of repeating them. If the User interface changes, Omit<User, "id"> updates automatically everywhere.

The satisfies operator checks that a value matches a type without widening it, so you keep precise literal types (and autocomplete) while still getting validation.

utility-types.tsTypeScript
interface User {
  id: number;
  name: string;
  email: string;
  passwordHash: string;
  createdAt: Date;
}

type NewUser = Omit<User, "id" | "createdAt">;     // input for create
type UserUpdate = Partial<Pick<User, "name" | "email">>;
type PublicUser = Omit<User, "passwordHash">;      // safe to send to clients

function toPublic({ passwordHash: _omit, ...rest }: User): PublicUser {
  return rest;
}

async function loadUser(id: number) {
  return { id, name: "Amina", email: "amina@example.com", passwordHash: "x", createdAt: new Date() };
}
type Loaded = Awaited<ReturnType<typeof loadUser>>; // the resolved object type

type Theme = "light" | "dark";
const palette = {
  light: { bg: "#ffffff", text: "#1B2435" },
  dark: { bg: "#0E1826", text: "#E7ECF6" },
} satisfies Record<Theme, { bg: string; text: string }>;

const draft: NewUser = { name: "Juma", email: "juma@example.com", passwordHash: "h" };
const patch: UserUpdate = { email: "new@example.com" };

loadUser(1).then((u: Loaded) => {
  console.log(toPublic(u), draft.name, patch, palette.dark.bg);
});

Key points

  • Derive related types (create, update, public) from one base type.
  • Omit sensitive fields to build types that are safe to expose.
  • satisfies validates a value while preserving its exact inferred type.

Exercise

Given a Product interface, derive CreateProductInput, UpdateProductInput and ProductSummary (id, name, price only). Write a const route table with satisfies Record<string, { method: "GET" | "POST"; auth: boolean }>.

Show solution

Try the exercise yourself first — then compare your approach with this one.

Each input type is derived from the one Product interface, so changing Product updates them all. The route table uses satisfies: every entry is checked against the allowed shape, yet routes keeps its exact keys, so a typo like routes["/prodcts"] is still caught.

products-types.tsTypeScript
interface Product {
  id: number;
  name: string;
  price: number;
  stock: number;
  createdAt: Date;
}

type CreateProductInput = Omit<Product, "id" | "createdAt">;
type UpdateProductInput = Partial<CreateProductInput>;
type ProductSummary = Pick<Product, "id" | "name" | "price">;

const routes = {
  "/products": { method: "GET", auth: false },
  "/products/new": { method: "POST", auth: true },
} satisfies Record<string, { method: "GET" | "POST"; auth: boolean }>;

const input: CreateProductInput = { name: "Geometry set", price: 4500, stock: 40 };
const patch: UpdateProductInput = { price: 4000 };
const summary: ProductSummary = { id: 1, name: input.name, price: patch.price ?? input.price };

console.log(summary, routes["/products/new"].auth);   // { id: 1, name: 'Geometry set', price: 4000 } true

Check your understanding

  1. Which utility type makes every property optional — ideal for an update payload?

  2. How do you build a user type that is safe to send to clients, without the password hash?

  3. What is the result type of Awaited<ReturnType<typeof loadUser>> when loadUser is async and returns a user object?

  4. What does satisfies do that a type annotation does not?

Ask AI