-
Section 1: Getting Started
-
Section 2: Basic Types
-
Section 3: Functions and Objects
-
Section 4: Advanced Types
-
Section 5: Object-Oriented TypeScript
-
Section 6: Working with Modules
-
Section 7: Tooling and Practice
-
Section 8: Type-Level Programming
-
Section 9: TypeScript with Backends
-
Section 10: Testing Typed Code
-
Section 11: Data Validation with Types
-
Section 12: Practical Projects
-
Section 13: Compiler Internals
-
Section 14: Configuration Deep Dive
-
Section 15: Enums, Symbols, and Special Types
-
Section 16: Working with Async Code
-
Section 17: TypeScript and the DOM
-
Section 18: Advanced Generics Practice
-
Section 19: Working with Third-Party Types
-
Section 20: Monorepo and Large-Scale Practices
-
Section 21: Common Pitfalls and Best Practices
-
Section 22: Interview Practice
-
Section 23: Handbook: Narrowing In Depth
-
Section 24: Handbook: Object Types In Depth
-
Section 25: Handbook: Classes In Depth
-
Section 26: Handbook: Modules In Depth
-
Section 27: Handbook: Declaration Files In Depth
-
Section 28: JSX and Namespaces
-
Section 29: Compiler Configuration Reference
-
Section 30: More Practice Exercises
-
Section 31: Handbook: Everyday Types Deep Dive
-
Section 32: Utility Types Full Reference
-
Section 33: Decorators Reference
-
Section 34: Mixins and Advanced OOP Patterns
-
Section 35: Iterators and Generators Typing
-
Section 36: More Type-Level Programming Practice
-
Section 37: TypeScript Ecosystem Tools
-
Section 38: TypeScript with Testing Frameworks
-
Section 39: TypeScript for Library Authors
-
Section 40: More Interview Practice
-
Section 41: Handbook: Functions In Depth
-
Section 42: Handbook: Type Manipulation Deep Dive
-
Section 43: More Real-World Patterns
-
Section 44: TypeScript Release Notes Highlights
-
Section 45: Final Practice and Review
33: Utility Types: Partial, Pick, Omit, Record
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
ProductUpdateto usePartial. - Convert
ProductPreviewto usePick. - Convert
PublicProductto useOmit. - Convert the
productCatalogdeclaration to useRecord.
There are no comments for now.