Record<K, V>
One rule for all keys, one shape for all values
Imagine you are managing the shop registry at Laad Bazaar near Charminar. Every shop has a number — that is the key. And every number maps to a shop entry with the owner's name, items sold, and monthly rent — that is the value.
The registrar does not care about specific shop numbers. They just enforce one simple rule: "Every number maps to a ShopEntry." Shop 1 sells bangles, Shop 2 sells pearls, Shop 3 sells zari thread — but they ALL follow the same shape.
This is exactly what Record<K, V> does in TypeScript. It creates an object type where every key of type K maps to a value of type V. It is the cleanest way to define dictionary-like types, lookup tables, and keyed collections.
type ShopEntry = {
owner: string;
items: string;
rent: number;
};
type ShopRegistry =
Record<string, ShopEntry>;
No need to write out each property manually. The registrar's jugaad: one rule for all shops! Whether you have 10 shops or 500, the type stays exactly the same. You do not repeat yourself.
Seedha samjho: Record = "har ek key ka ek value type hai, poora lafda ek line mein solve!" Every key gets the same value shape, every single time. That is the power and simplicity of Record — it takes a messy dictionary problem and makes it tidy with one line of code.
The syntax is beautifully simple: Record<K, V> where K is the key type and V is the value type. Let us see it in action with progressively more powerful examples.
Basic: string keys to number values
const scores: Record<string, number> =
{ math: 90, science: 85, eng: 92 };
Every string key maps to a number. Simple and clean. You can add any string key — "math", "science", "gym" — as long as the value is a number. This is perfect for simple lookup tables and score cards.
Number keys to string values
const users: Record<number, string> =
{ 1: "Imran", 2: "Sana", 3: "Ravi" };
Now the keys are numbers. This looks like an array but it is a proper object with numeric keys. Useful for ID-based lookups where you need to map employee IDs or roll numbers to names.
The real magic: literal key unions
type Role = "admin" | "user" | "guest";
type Permissions =
Record<Role, string[]>;
const perms: Permissions = {
admin: ["read", "write", "delete"],
user: ["read", "write"],
guest: ["read"]
};
Now TypeScript enforces that ALL three keys exist. Forget "guest"? Error. Add "superuser"? Error. This is the BEST use of Record — with a literal key union, you get compile-time safety that every key is present and every value matches the correct type. No more typos, no more missing keys.
Equivalent to index signature, but cleaner:
// These are equivalent:
type A = Record<string, number>;
type B = { [key: string]: number };
// A is way cleaner to read!
Record is just syntactic sugar over index signatures for simple cases — but with union keys, it becomes far more powerful than any index signature could ever be. This is why Record is the preferred choice in most TypeScript codebases.
There are three ways to create key-value types in TypeScript. Each has different strengths, and knowing when to use which is a hallmark of a senior developer.
1. Record<K, V> — The Clean Default
type UserMap = Record<string, User>;
const users: UserMap = {
imran: { name: "Imran", age: 28 },
sana: { name: "Sana", age: 25 }
};
Plain object, JSON serializable, no API methods. Best when you know the exact key set (literal union) or want a simple string-keyed dictionary. This is the most common choice for typed objects in real codebases.
2. Index Signature — For Truly Dynamic Keys
type DynamicCache = {
[key: string]: User;
};
// Same as Record<string, User>
// But can add modifiers:
type PartialCache = {
[key: string]: User | undefined;
};
Same as Record<string, V> but more verbose. Does not enforce specific keys. Use when you truly cannot enumerate the keys — like a cache that grows dynamically at runtime with unpredictable key names.
3. Map<K, V> — The Runtime Powerhouse
const userMap = new Map<string, User>();
userMap.set("imran", { name: "Imran", age: 28 });
userMap.has("imran"); // true
userMap.get("imran"); // { name: "Imran", age: 28 }
userMap.delete("imran");
userMap.size; // 0
Full API with .get(), .set(), .has(), .delete(), .size. Iterable, remembers insertion order. But NOT JSON serializable — you cannot send a Map in an API response without converting it first.
When to use each:
- Record — Fixed-shape dictionaries, config objects, API responses. 90% of your use cases.
- Index signature — Dynamic keys you cannot enumerate. Rare but real.
- Map — Runtime operations: adding, removing, iterating, checking existence frequently.
Think of it like Hyderabad transport: Record is the MMTS train — fixed route, reliable, everyone uses it. Index signature is an auto — goes anywhere but no fixed stops. Map is your own bike — full control, but you maintain it!
Record is powerful, but it has sharp edges that trip up even experienced developers. Let us walk through the most common traps one by one.
Trap 1: Record<string, V> allows ANY string
const r: Record<string, number> = {};
r.anyRandomKey = 42; // No error!
r.whatIsThis = 99; // Still no error!
With string as the key type, TypeScript will not complain about any key name. If you need restricted keys, use a literal union: Record<"a" | "b", number>. This is the number one mistake developers make — they think Record restricts keys by default. It does not, unless you use a union type!
Trap 2: Record ENFORCES all keys with unions
type RGB = Record<"r" | "g" | "b", number>;
const color: RGB = {
r: 255,
g: 0
// Error: Property 'b' is missing!
};
This is a FEATURE, not a bug — but it surprises beginners every time. When K is a union, you MUST provide every single key. If some keys should be optional, wrap it: Partial<Record<"r"|"g"|"b", number>>. This pattern is common in config objects where not every field is always needed.
Trap 3: Keys must be string | number | symbol
type Bad = Record<boolean, string>;
// Error: Type 'boolean' does not
// satisfy the constraint
// 'string | number | symbol'
You cannot use boolean, object, or any complex type as a Record key. JavaScript object keys can only be strings, numbers, or symbols — and Record follows the exact same rule at the type level.
Trap 4: Record has no runtime methods
const data: Record<string, User> = {};
data.get("key"); // Error! No .get()
data.has("key"); // Error! No .has()
Record is a TYPE, not a class. It compiles to a plain object. No .get(), .has(), .keys() — none of that. If you need runtime methods, use Map instead. Record gives you type safety, not runtime functionality.
Trap 5: Nested Records get hard to read
// Valid but yikes:
type Nested =
Record<string, Record<string, number>>;
// Better: use type aliases
type Inner = Record<string, number>;
type Better = Record<string, Inner>;
Nested Records are perfectly valid TypeScript but become unreadable quickly. Always extract inner records into named type aliases. Your future self will thank you, and your teammates will not curse you in code reviews!
Let us lock in everything about Record<K, V> with a quick cheatsheet you can come back to anytime.
Core Syntax
Record<K, V>
// K = key type (string | number | symbol)
// V = value type (anything)
Basic Usage
// Any string key → number value
type Scores = Record<string, number>;
// Number keys → string value
type Names = Record<number, string>;
With Literal Union (BEST USE)
type Role = "admin" | "user" | "guest";
type Perms = Record<Role, string[]>;
// All three keys REQUIRED!
Nested Record
type Inner = Record<string, number>;
type Matrix = Record<string, Inner>;
Equivalents
Record<string, V>
// is the same as:
{ [key: string]: V }
// Record is just cleaner!
Record vs Map Quick Compare
- Record — Type-only, plain object, JSON-friendly, no runtime API.
- Map — Runtime, has
.get()/.set(), iterable, not JSON serializable.
5 Key Rules
- Use literal key unions for restricted, enforced keys.
- Use
stringfor open/dynamic keys (but know it allows anything). - All union keys are REQUIRED with Record — no skipping!
- Keys must be
string | number | symbol— no booleans or objects. - Use
Partial<Record<K, V>>when some keys should be optional.
The golden rule: "Record is the Laad Bazaar registry — one rule for all shops, every number maps to an entry. Clean, simple, jugaad complete, bhai!"
Key Points
- Record
creates object types where every key K maps to value V - Record
is equivalent to { [key: string]: V } but cleaner - With literal union keys, Record ENFORCES all keys are present
- Keys must be string | number | symbol — no boolean or object keys
- Use Partial
> for optional keys - Record is type-only; use Map for runtime methods like .get() and .has()
Want to track your progress?
Log in to save your place and pick up where you left off.
Progress track karna chahte ho?
Login karo apni progress save karne ke liye aur jahan chhoda tha wahan se shuru karo.
Login