Skip to main content

Record

When you need an object that acts as a dictionary or a map, where the keys and values follow a consistent pattern, Record is the utility you reach for.

The mental model

JavaScript objects are frequently used as key-value stores. In TypeScript, you need a way to say "I don't know the exact keys in advance, but whatever they are, they must be of type X, and their values must be of type Y."

Record<Keys, Type> constructs an object type whose property keys are Keys and whose property values are Type.

// Keys are strings, values are numbers
const scores: Record<string, number> = {
alice: 10,
bob: 20,
};

scores.charlie = 30; // Valid
// scores.dave = "forty"; // Error: Type 'string' is not assignable to 'number'

Constraining keys with Unions

The true power of Record emerges when you constrain the keys to a specific union of strings, rather than allowing any arbitrary string. This forces the object to be exhaustive.

type Role = "admin" | "editor" | "viewer";

// We MUST provide a boolean for every role
const permissions: Record<Role, boolean> = {
admin: true,
editor: true,
viewer: false,
};

If we add a new role like "guest" to the Role type, TypeScript will immediately flag an error on permissions because it is missing the guest key. This is a massive win for maintainability.

Record vs Index Signatures

You might have seen index signatures like { [key: string]: number }.

Record<string, number> is essentially shorthand for that. However, Record is generally preferred when mapping over specific literal types (like the Role example above) because index signatures do not enforce exhaustiveness.

Use Record<Keys, Value> to define dictionaries. Constrain the Keys parameter with a union type to guarantee that every possible key is accounted for at compile time.