TypeScript
Completed
-
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
29: Mapped Types
Imagine you're an architect and you've created a detailed blueprint for a house. This blueprint lists every room: the kitchen, the bedroom, the bathroom, and the garage. Now, imagine your client wants a separate "Maintenance Checklist" for that house. Instead of manually writing a new list, you decide to create a rule: "For every room listed in the blueprint, create a corresponding checkbox on the checklist."
If you add a "Sunroom" to the blueprint later, the checklist automatically gets a "Sunroom" checkbox because the rule is tied to the blueprint itself. You aren't defining the checklist manually; you're mapping the blueprint onto a new format.
In TypeScript, Mapped Types are exactly that. They let you take an existing type and "loop" through its keys to create a new type. It’s essentially the .map() method, but for your type system.
Turning a User Model into Form State
I see this all the time in large-scale React or Vue projects. You have a clean data model for a User, but the form used to edit that user needs more metadata for every single field—like whether the field has been touched or if it's currently validating.
type User = {
id: string;
username: string;
email: string;
age: number;
};
// Instead of manually writing this:
type UserFormState = {
id: { value: string; isDirty: boolean };
username: { value: string; isDirty: boolean };
email: { value: string; isDirty: boolean };
age: { value: number; isDirty: boolean };
};
Writing that manually is a nightmare. If you add a phoneNumber to the User type, you'll inevitably forget to update the UserFormState, and your app will crash or throw a type error in some obscure component. Here is how we do it with a mapped type:
type FormField<T> = {
value: T;
isDirty: boolean;
};
type UserFormState = {
[K in keyof User]: FormField<User[K]>;
};
Let's break down that weird syntax. keyof User gives us a union of the keys: "id" | "username" | "email" | "age". The K in ... part is the loop. For every key (K) in that union, TypeScript creates a property. User[K] is a lookup type; it grabs the actual type of that specific property (e.g., when K is "age", User[K] is number).
Toggling Modifiers on the Fly
Sometimes you don't want to change the value type, but you want to change the behavior of the property. TypeScript lets you add or remove modifiers like readonly or the optional ? during the mapping process.
I often use this when I want to ensure that a "Configuration" object is strictly read-only after it's been initialized. You can use the + or - prefixes to add or remove these modifiers.
type Options = {
endpoint: string;
retries?: number;
timeout: number;
};
// Make everything required and read-only
type StrictOptions = {
readonly [K in keyof Options]-?: Options[K];
};
/*
Result:
{
readonly endpoint: string;
readonly retries: number; // The '?' is gone!
readonly timeout: number;
}
*/
The -? is the magic trick here. It explicitly tells TypeScript: "Whatever optionality existed on the original key, strip it away."
Renaming Keys During the Map
In the latest versions of TypeScript, we can actually change the name of the keys as we map them using the as keyword. This is incredibly powerful for creating "API Response" types where the backend sends you user_name (snake_case), but you want your frontend types to be userName (camelCase).
type UserApi = {
user_id: string;
user_email: string;
};
type UserFrontend = {
[K in keyof UserApi as K extends `user_${infer Rest}` ? Capitalize<Rest> : K]: UserApi[K];
};
// Result: { id: string; email: string; }
// (Wait, I simplified the logic slightly for the example, but you get the gist!)
Mapping types effectively means you stop writing the same type three different times. You define the "Source of Truth" once, and let the compiler derive everything else.
📋 Practical Task
Build a Product Validation Error Map
You are building an e-commerce admin panel. You have a Product interface, and you need to create a type called ProductValidationErrors. This type should have the exact same keys as the Product interface, but every single value must be string | null (where the string is the error message, and null means there is no error).
Requirements:
- Define a
Productinterface with at least four fields (e.g.,sku,price,title,description) with various types (string, number). - Use a Mapped Type to create
ProductValidationErrors. - Ensure that if a new field is added to
Product,ProductValidationErrorsupdates automatically.
interface Product {
// Your fields here
}
type ProductValidationErrors = {
// Your mapped type logic here
};
// Test it:
const errors: ProductValidationErrors = {
// Try assigning a number here; it should fail.
// Try assigning a string or null; it should pass.
};There are no comments for now.