[key: string]: V is tedious. TypeScript’s built-in Record<K, V> utility constructs strict dictionary shapes instantly.1. Simple Dictionaries (Record<string, V>)#
When you don’t know the exact names of the keys in advance, but you know that every key must map to a specific value type, use Record<string, V>.
// Any string key is permitted, but the value MUST be a number.
const productPrices: Record<string, number> = {
apple: 1.99,
banana: 0.99,
laptop: 1500,
};
// 🟢 Valid additions
productPrices.orange = 2.50;
productPrices["mechanical_keyboard"] = 120;
// ❌ Compiler Error: Type 'string' is not assignable to type 'number'.
// productPrices.watermelon = "three dollars";
Under the Hood Comparison#
Record<string, number> is perfectly equivalent to writing a manual Index Signature, but it is vastly more readable:
// These two types are structurally identical:
type MapA = Record<string, number>;
type MapB = { [key: string]: number };2. Exhaustive Mapping with Unions#
The true power of Record<K, V> unlocks when K is constrained to a specific Union Type.
When you pass a Union as the Key parameter, TypeScript enforces that every single member of the union MUST exist as a key in the resulting object. This prevents missing configuration keys.
type Environment = "development" | "staging" | "production";
// 🟢 The object MUST implement ALL three environments!
const apiEndpoints: Record<Environment, string> = {
development: "http://localhost:3000",
staging: "https://staging.api.com",
production: "https://api.com",
};
// ❌ Compiler Error: Property 'production' is missing in type '{ ... }'
/*
const badEndpoints: Record<Environment, string> = {
development: "http://localhost:3000",
staging: "https://staging.api.com",
};
*/3. Combining Record with Other Utility Types#
Because utility types are generic definitions, they compose beautifully. You can build advanced domain constraints by nesting Record, Partial, and Readonly.
Example: Role-Based Permission Matrix#
Let’s construct an access control matrix mapping user roles to specific permission flags. We want to ensure that:
- Every role in the system is accounted for (Exhaustive Keys).
- The permission matrix is immutable at runtime (Readonly).
type UserRole = "admin" | "editor" | "viewer";
interface Permissions {
canDelete: boolean;
canEdit: boolean;
canRead: boolean;
}
// 🟢 Compose: A Record of Roles mapped to Readonly Permissions
type AclMatrix = Record<UserRole, Readonly<Permissions>>;
const acl: AclMatrix = {
admin: { canDelete: true, canEdit: true, canRead: true },
editor: { canDelete: false, canEdit: true, canRead: true },
viewer: { canDelete: false, canEdit: false, canRead: true },
};
// ❌ Compiler Error: Cannot assign to 'canEdit' because it is a read-only property.
// acl.viewer.canEdit = true;
4. Partial<Record<K, V>> (Optional Dictionaries)#
If you are mapping a Union but you do not want to enforce that every key must be present (e.g., an overriding configuration where missing keys default to standard behavior), wrap the Record in Partial:
type UIComponent = "button" | "header" | "footer" | "sidebar";
// We don't have to provide a style override for every component.
type ThemeOverrides = Partial<Record<UIComponent, string>>;
const myTheme: ThemeOverrides = {
button: "bg-blue-500 text-white", // Only overriding the button style
// header, footer, and sidebar are safely omitted.
};5. How Record<K, V> is Defined Under the Hood#
To understand how Record enforces these rules, look at its core TypeScript definition:
type CustomRecord<K extends keyof any, T> = {
[P in K]: T;
};K extends keyof any: The Key typeKis constrained to valid object keys (string | number | symbol).[P in K]: T: It loops over every propertyPin the unionK, and assigns it the exact value typeT.
Summary & Next Steps#
In this episode:
- We modeled arbitrary dictionaries using
Record<string, V>. - We enforced exhaustive key mapping by passing Unions as the Key parameter (
Record<Union, V>). - We composed
RecordwithReadonly<T>for access matrices andPartial<T>for optional configurations. - We analyzed the internal mapped type definition powering
Record.
In Episode 36: Utility Types (Extract and Exclude), we will transition from transforming Objects to transforming Union types!

