Skip to Content
Course content

33: Utility Types: Partial, Pick, Omit, Record

Click on the "Edit" button in the top corner of the screen to edit your slide content.

Imagine you're ordering a custom burger at a restaurant. The menu has a "Standard Deluxe Burger" that comes with a patty, cheese, lettuce, tomato, onion, and a bun. That's your base definition. But in the real world, you rarely just want the "Standard" every single time.

Sometimes, you just want to tell the waiter, "I want the Deluxe, but swap the onion for extra pickles." You aren't redefining the whole burger; you're just providing a few partial updates. Other times, the kitchen needs a "simplified" version of the order for the prep station—they only care about the patty and the bun (picking specific parts) and don't care about the garnish. Or, maybe the manager needs a list of all burgers, but they want to omit the cost from the view so the chefs don't get distracted by the money. Finally, the restaurant keeps a logbook where the key is the order number and the value is the full burger details (a record of orders).

In TypeScript, we do the exact same thing with Utility Types. Instead of writing five different interfaces for the same object just to change a few properties, we take one "source of truth" interface and transform it on the fly.

Making fields optional with Partial

Let's say we have a UserProfile. When a user signs up, they provide everything. But when they go to their "Settings" page to update their bio, they might only change their location. They aren't sending the whole profile back to your API—that would be wasteful.

interface UserProfile {
  id: string;
  username: string;
  email: string;
  bio: string;
  location: string;
}

// I don't want to force the user to provide every field just to update one.
function updateProfile(id: string, updates: Partial<UserProfile>) {
  // 'updates' now behaves as if every property in UserProfile is optional (?)
  console.log(`Updating user ${id} with`, updates);
}

updateProfile("u123", { location: "New York" }); // This is perfectly valid!

Partial<T> essentially takes every property in your type and slaps a ? next to it. It's a lifesaver for "Patch" requests in REST APIs.

Slicing out exactly what you need with Pick

Now, imagine you're building a "User Card" component for a sidebar. You don't need the user's email or their full bio—that would bloat your component's props and potentially expose data you don't need in that specific UI slice.

type UserSummary = Pick<UserProfile, 'username' | 'location'>;

const userCard: UserSummary = {
  username: "coder_jane",
  location: "Berlin"
};

Pick<T, K> lets you create a new type by choosing only the keys you want. I prefer this over creating a UserSummary interface manually because if I ever rename username to handle in the main UserProfile, TypeScript will automatically update the UserSummary type too. No hunting through files to rename strings.

Stripping away the noise with Omit

Omit is the mirror image of Pick. Instead of saying "I want these three things," you're saying "I want everything except these two things." This is incredibly useful for security. You might have a UserAccount that includes a password hash, but you want to pass that object to a frontend function without the sensitive bits.

interface UserAccount {
  id: string;
  username: string;
  email: string;
  passwordHash: string; // Super sensitive!
}

type UserSafeView = Omit<UserAccount, 'passwordHash'>;

const publicProfile: UserSafeView = {
  id: "u1",
  username: "dev_dan",
  email: "dan@example.com"
  // passwordHash: "..." // TypeScript will throw an error here. Nice.
};

Mapping dynamic keys with Record

Finally, we have Record<K, T>. This isn't about transforming an existing object, but rather defining a map. If you're storing a collection of users in a state object where the keys are the user IDs, you don't want to use any for the keys.

// Key is a string (the ID), Value is the UserAccount object
const userCache: Record<string, UserAccount> = {
  "u1": { id: "u1", username: "alice", email: "a@test.com", passwordHash: "abc" },
  "u2": { id: "u2", username: "bob", email: "b@test.com", passwordHash: "xyz" },
};

// You can also use a union of strings for more strictness
type Page = 'home' | 'about' | 'contact';
const pageViews: Record<Page, number> = {
  home: 100,
  about: 50,
  contact: 20
};

I use Record constantly for configuration objects or lookup tables. It's much cleaner than declaring an index signature manually.




📋 Practical Task

Refactoring the E-Commerce Product Manager

You are working on a product management system. You have a master Product interface, but your current code is redundant because you've created separate interfaces for updates, summaries, and caches.

Your Goal: Refactor the following code to use Partial, Pick, Omit, and Record to remove the redundant interfaces while keeping the type safety intact.

interface Product {
  id: string;
  name: string;
  description: string;
  price: number;
  stock: number;
  internalSupplierCode: string;
}

// TODO: Replace these redundant interfaces with Utility Types
interface ProductUpdate {
  name?: string;
  description?: string;
  price?: number;
  stock?: number;
  internalSupplierCode?: string;
}

interface ProductPreview {
  name: string;
  price: number;
}

interface PublicProduct {
  id: string;
  name: string;
  description: string;
  price: number;
  stock: number;
}

// Implementation to fix:
function updateProduct(id: string, changes: ProductUpdate) {
  console.log(`Updating ${id}...`);
}

function displayPreview(product: ProductPreview) {
  console.log(`${product.name}: $${product.price}`);
}

const productCatalog: { [key: string]: Product } = {};

Requirements:

  • Convert ProductUpdate to use Partial.
  • Convert ProductPreview to use Pick.
  • Convert PublicProduct to use Omit.
  • Convert the productCatalog declaration to use Record.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.