Suggest an editImprove this articleRefine the answer for “Utility type record in TypeScript”. Your changes go to moderation before they’re published.Approval requiredContentWhat you’re changing🇺🇸EN🇺🇦UAPreviewTitle (EN)Short answer (EN)**Record<K, T>** is a TypeScript utility type that creates an object where all keys from union K are required and map to type T. ```typescript type Status = "pending" | "approved" | "rejected"; const config: Record<Status, string> = { pending: "Waiting", approved: "Done", rejected: "Denied", }; ``` **Key:** Unlike index signatures, Record enforces that every key from the union must be present.Shown above the full answer for quick recall.Answer (EN)Image**Record<K, T>** is a TypeScript utility type that creates an object type where every key from union K must be present and maps to type T. ## Theory ### TL;DR - Analogy: Record is like a spreadsheet template where column headers are fixed and every cell holds the same data type. - Main difference from `{ [key: string]: T }`: Record forces ALL keys from your union to exist in the object; index signatures accept any string with no enforcement. - Internally, `Record<K, T>` compiles to `{ [P in K]: T }`, which is a mapped type. - Decision rule: use Record when keys are known and finite. Use index signatures when keys are open-ended or dynamic. ### Quick example ```typescript type Status = "pending" | "approved" | "rejected"; type StatusConfig = Record<Status, { label: string; color: string }>; const config: StatusConfig = { pending: { label: "Waiting", color: "#FFA500" }, approved: { label: "Done", color: "#00AA00" }, rejected: { label: "Denied", color: "#FF0000" } // Drop any key and TypeScript throws an error immediately. }; ``` All three keys must be present. That is the whole contract. ### Key difference Record guarantees exhaustiveness. Define `Record<"a" | "b" | "c", T>` and TypeScript forces all three keys at compile time. An index signature like `{ [key: string]: T }` accepts any string with no enforcement at all. Record makes contracts explicit. Index signatures offer flexibility. ### When to use - Enum-like configs: role names to permissions, status codes to messages, feature flags to booleans. - Lookup tables: user IDs to profiles, currency codes to exchange rates. - State machines: each state mapped to its allowed transitions or handler functions. - Skip Record when keys are truly dynamic or unbounded. An index signature or `Map` fits better there. ### How the compiler handles this TypeScript treats `Record<K, T>` as shorthand for `{ [P in K]: T }`. The compiler iterates through each member of union K and creates a required property. Omit any key and it reports an error. At runtime there is zero overhead. Record compiles to a plain JavaScript object. ### Common mistakes **Mistake 1: Forgetting a key after extending the union** The most common one I see after code reviews: adding a new value to the union but forgetting to update the config object. ```typescript type Permission = "read" | "write" | "delete" | "admin"; type PermissionConfig = Record<Permission, boolean>; // TypeScript error: property 'admin' is missing const config: PermissionConfig = { read: true, write: true, delete: true, }; // Fix: add all keys const configFixed: PermissionConfig = { read: true, write: true, delete: true, admin: false, }; ``` **Mistake 2: Using Record when keys should be optional** ```typescript type Feature = "darkMode" | "analytics" | "beta"; // Record requires all keys, so this fails: const flags: Record<Feature, boolean> = { darkMode: true, analytics: true, // Error: missing "beta" }; // Fix: wrap with Partial const flagsFixed: Partial<Record<Feature, boolean>> = { darkMode: true, analytics: true, // Valid now }; ``` **Mistake 3: Using `Record<string, T>` when keys are actually known** ```typescript // This is just an index signature under a different name type D = Record<string, number>; // Same as { [key: string]: number } // When keys are known, be explicit type Currency = "USD" | "EUR" | "GBP"; const rates: Record<Currency, number> = { USD: 1.0, EUR: 0.92, GBP: 0.87, // TypeScript forces all three }; ``` ### Real-world usage - React: component registries `Record<ComponentName, ComponentType>` - Redux: action type to handler mappings `Record<ActionType, ActionCreator>` - Express: HTTP method handlers `Record<"GET" | "POST" | "DELETE", RequestHandler>` - Testing: mock data factories `Record<UserRole, MockUser>` ### Follow-up questions **Q:** What is the difference between `Record<K, T>` and `{ [P in K]: T }`? **A:** They produce identical types. Record is syntactic sugar for the mapped type. Use Record for simple key-value contracts. Write the mapped type directly when you need per-key conditional logic, like `{ [P in K]: P extends "admin" ? AdminConfig : UserConfig }`. **Q:** How do you make some Record keys optional? **A:** Use `Partial<Record<K, T>>` to make all keys optional. Or use `Record<K, T | undefined>` if you want every key present but allow undefined values. **Q:** When would you pick `Map<string, T>` over `Record<string, T>`? **A:** Record works best for static, typed configurations that compile to plain objects. Map handles dynamic, unbounded keys and has built-in iteration helpers. For config objects use Record. For caches or user-generated key-value pairs, Map fits better. **Q:** What happens if you add an extra key at runtime? **A:** JavaScript allows it. Record only enforces the contract at compile time. TypeScript flags `config.newKey = "value"` as a type error, but the runtime assignment still executes. ## Examples ### Role permissions map ```typescript type UserRoles = "admin" | "user" | "guest"; type RolePermissions = Record<UserRoles, string[]>; const rolePermissions: RolePermissions = { admin: ["create", "edit", "delete"], user: ["view", "edit"], guest: ["view"], }; // TypeScript knows this is string[], not string[] | undefined console.log(rolePermissions["admin"]); // ["create", "edit", "delete"] ``` Every key from `UserRoles` is required. Add `"moderator"` to the union and TypeScript immediately asks for a `moderator` property in the object. ### Express route handlers ```typescript import { Request, Response } from "express"; type RouteHandlers = Record< "GET" | "POST" | "DELETE", (req: Request, res: Response) => void >; const handlers: RouteHandlers = { GET: (req, res) => res.json({ data: [] }), POST: (req, res) => res.status(201).json({}), DELETE: (req, res) => res.status(204).send(), }; ``` Adding a new HTTP method to the union means you must implement its handler before the code compiles. The type contract enforces complete coverage.For the reviewerNote to the moderator (optional)Visible only to the moderator. Helps review go faster.