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
184: Record<Keys, Type> In Depth
I see this all the time when I'm reviewing PRs from developers moving into TypeScript: they treat Record<K, T> as if it's just a stylistic preference—a "shorter way" to write an index signature. They'll write Record<string, number> thinking they've done something sophisticated, when in reality, they've just written { [key: string]: number } in a different syntax.
The misconception is that Record is a tool for flexibility. In truth, the real power of Record is constraint.
The Myth: Record is just a shorthand for Index Signatures
If you use string or number as your key type, Record doesn't actually give you any extra safety. Look at this:
type Inventory = Record<string, number>;
const stock: Inventory = {
apples: 10,
oranges: 20
};
// TypeScript is perfectly happy with this, but it's essentially a wild west.
// Any string is a valid key.
console.log(stock.bananas); // undefined, but no compiler error.
When you use string, you're telling TypeScript, "I have no idea what the keys will be, just that they'll be strings." That's fine for a generic cache or a dictionary, but it's not where Record earns its keep. If this is all you're doing, you're missing the point of the utility type.
The Reality: Enforcing Exhaustive Mappings
Where Record becomes a superpower is when you pair it with a Union Type. This allows you to tell TypeScript, "This object must have exactly these keys—no more, no less."
Imagine we're building a permission system for a CMS. We have a set of roles, and every single role must have a defined set of permissions. I don't want to accidentally forget to define permissions for a new role I just added to the system.
type UserRole = 'admin' | 'editor' | 'viewer';
interface Permissions {
canEdit: boolean;
canDelete: boolean;
canPublish: boolean;
}
// This is the magic part.
// We aren't just saying "keys are strings"; we're saying "keys must be UserRole".
const RolePermissions: Record<UserRole, Permissions> = {
admin: { canEdit: true, canDelete: true, canPublish: true },
editor: { canEdit: true, canDelete: false, canPublish: true },
// Oops! I forgot 'viewer'.
};
If you try to compile the code above, TypeScript will throw an error: Property 'viewer' is missing in type... but required in type 'Record<UserRole, Permissions>'. This is massive. I've had countless bugs in my career where a new enum value was added to a codebase, but the corresponding configuration object wasn't updated. Record kills that entire class of bug.
Combining Record with Keyof for Dynamic Constraints
Sometimes you don't want to hardcode a union. Instead, you want a record that maps to the keys of another existing type. This is a pattern I use constantly when creating "lookup" tables or strategy patterns.
interface Product {
id: string;
price: number;
name: string;
}
// I want a way to format each property of the Product object.
// Instead of writing a custom type, I use keyof Product.
const ProductFormatters: Record<keyof Product, (val: any) => string> = {
id: (val) => `ID: ${val}`,
price: (val) => `$${val.toFixed(2)}`,
name: (val) => val.toUpperCase(),
};
By using keyof Product, the ProductFormatters object is now tethered to the Product interface. If I add a description field to the Product interface tomorrow, TypeScript will immediately scream at me that ProductFormatters is missing the description handler. It turns your type system into a TODO list.
📋 Practical Task
Exercise: Build an HTTP Status Category Mapper
You are building a monitoring tool that groups HTTP status codes into categories. You need to ensure that every single "critical" status code is accounted for in your mapping object so that no error goes uncategorized.
Requirements:
- Create a union type called
CriticalStatusCodecontaining the following numbers:400, 401, 403, 404, 500, 503. - Create a type called
StatusCategorywhich is a union of'Client Error'and'Server Error'. - Implement a constant called
StatusCodeMapusingRecordthat maps everyCriticalStatusCodeto aStatusCategory. - Ensure the
StatusCodeMapis exhaustive (if you miss one of the status codes, the compiler should complain).
// Your code here
There are no comments for now.