๐ŸŽ“ A1Learner
๐Ÿ”
0 of 42 lessons completed

TypeScript Utility Types in Use

Utility types reshape an existing type without rewriting it. Partial, Required and Readonly each change how the members of a value may be used.

The patch pattern with Partial

The update function takes an id and a partial patch. It applies the patch over the stored record and returns the merged result.

interface StoredUser {
  id: number;
  displayName: string;
  email: string;
}
const stored: StoredUser = {
  id: 1,
  displayName: "Asha",
  email: "asha@example.com",
};
function updateStored(id: number, patch: Partial<StoredUser>): StoredUser {
  return { ...stored, ...patch, id };
}
const updated = updateStored(1, { email: "asha.rao@example.com" });
console.log(updated.id);
console.log(updated.displayName);
console.log(updated.email);

Required as the gate before saving

A draft is built loosely with optional members. The save function accepts only a Required value, so every member must be present first.

interface DraftArticle {
  title?: string;
  body?: string;
}
function saveArticle(article: Required<DraftArticle>): string {
  return "Saved " + article.title;
}
const draft: DraftArticle = { title: "Guide" };
console.log(draft.body);
const checked: Required<DraftArticle> = {
  title: "Guide",
  body: "Full text",
};
console.log(saveArticle(checked));
console.log(checked.body);

Readonly guards one level only

The address object sits inside a Readonly value. Changing its city still runs, and the output proves the change went through.

interface AddressBlock {
  city: string;
}
interface CustomerRecord {
  customerName: string;
  address: AddressBlock;
}
const customer: Readonly<CustomerRecord> = {
  customerName: "Asha",
  address: { city: "Pune" },
};
customer.address.city = "Mumbai";
console.log(customer.customerName);
console.log(customer.address.city);

Example

interface ProductRecord {
  id: number;
  title: string;
  price: number;
}
const product: ProductRecord = { id: 9, title: "Pen", price: 250 };
function patchProduct(
  record: ProductRecord,
  patch: Partial<ProductRecord>
): ProductRecord {
  return { ...record, ...patch };
}
const patched = patchProduct(product, { price: 199 });
const view: Readonly<ProductRecord> = patched;
console.log(view.id);
console.log(view.title);
console.log(view.price);

Key points

  • Partial<T> makes every member optional, which suits patch-style updates.
  • Required<T> removes optionality, which suits a gate before saving.
  • Readonly<T> guards only the top level of a value.
  • A nested object inside a Readonly value stays mutable.

In this example, a Partial patch lowers the price, and the merged record is then exposed through a Readonly view.

Note

Readonly is a checker guard, not a runtime freeze of the object.

Try it Yourself

Edit the code, then press Run.

Running is simulated ยท TypeScript Playground

Exercise

A Readonly value holds a nested address object. What happens when code changes the city inside that address?