Record<K, V> is the type of an object whose keys are of type K and whose values are of type V. {Record<string, number>} fits a stock table keyed by SKU; {Record<"S" | "M" | "L", number>} requires exactly those three keys.
Two older spellings mean the same as Record<string, number>: the index signature {{ [sku: string]: number }} and the Closure form {Object.<string, number>}. TypeScript accepts all three; Record is the shortest and works with a union of keys.
At a glance
| Use | JSDoc in a .js file | TypeScript in a .ts file |
|---|---|---|
| Any string key | /** @type {Record<string, number>} */ | : Record<string, number> |
| A fixed set of keys | /** @type {Record<"S" | "M" | "L", number>} */ | : Record<"S" | "M" | "L", number> |
| Index signature | /** @type {{ [sku: string]: number }} */ | : { [sku: string]: number } |
| Closure spelling | /** @type {Object.<string, number>} */ | : { [x: string]: number } |
Examples
/** @type {Record<string, number>} */
const stock = { "TEE-01": 12, "MUG-07": 3 };
stock["CAP-02"] = 8;
/** @param {Record<string, number>} levels */
export function lowStock(levels) {
return Object.entries(levels)
.filter(([, count]) => count < 5)
.map(([sku]) => sku);
}
lowStock(stock);const stock: Record<string, number> = { "TEE-01": 12, "MUG-07": 3 };
stock["CAP-02"] = 8;
export function lowStock(levels: Record<string, number>) {
return Object.entries(levels)
.filter(([, count]) => count < 5)
.map(([sku]) => sku);
}
lowStock(stock);jsconfig.json for this example
{
"compilerOptions": {
"checkJs": true,
"noEmit": true,
"strict": true,
"target": "es2022",
"module": "nodenext",
"types": []
},
"include": ["*.js"]
} /** @typedef {"S" | "M" | "L"} Size */
/** @type {Record<Size, number>} */
const stockBySize = { S: 4, M: 10 };error TS2741: Property 'L' is missing in type '{ S: number; M: number; }' but required in type 'Record<Size, number>'.
export { stockBySize };jsconfig.json for this example
{
"compilerOptions": {
"checkJs": true,
"noEmit": true,
"strict": true,
"target": "es2022",
"module": "nodenext",
"types": []
},
"include": ["*.js"]
} /** @type {Object.<string, number>} */
const closureStyle = { "TEE-01": 12 };
/** @type {{ [sku: string]: number }} */
const indexSignature = { "TEE-01": 12 };
/** @type {Record<string, number>} */
const record = { "TEE-01": 12 };
closureStyle["MUG-07"] = 3;
indexSignature["MUG-07"] = 3;
record["MUG-07"] = "3";error TS2322: Type 'string' is not assignable to type 'number'.
export { closureStyle, indexSignature, record };jsconfig.json for this example
{
"compilerOptions": {
"checkJs": true,
"noEmit": true,
"strict": true,
"target": "es2022",
"module": "nodenext",
"types": []
},
"include": ["*.js"]
} /** @type {Record<string, number>} */
const stock = { "TEE-01": 12 };
/** @param {string} sku */
export function remaining(sku) {
return stock[sku] - 1;error TS2532: Object is possibly 'undefined'.
}
/** @param {string} sku */
export function remainingOrZero(sku) {
return (stock[sku] ?? 0) - 1;
}jsconfig.json for this example
{
"compilerOptions": {
"checkJs": true,
"noEmit": true,
"strict": true,
"noUncheckedIndexedAccess": true,
"target": "es2022",
"module": "nodenext",
"types": []
},
"include": ["*.js"]
} A union of keys
With literal keys, Record is complete or it is an error: leaving out L from a Record<Size, number> is TS2741, as the second example shows. That makes it a good fit for lookup tables that must cover every case of a union.
Lookups that may miss
By default, stock[sku] on a Record<string, number> is typed number, even for a key that is not there. With noUncheckedIndexedAccess, TypeScript adds undefined to the result and asks for a check (TS2532 in the fourth example), which matches what happens at run time.
For a dictionary that changes a lot, a Map<string, number> is often clearer: get() is always typed as possibly undefined, and keys are not mixed with object properties.
Gotchas
Record<string, V>claims every string key exists. PreferPartial<Record<string, V>>orMapwhen that is not true andnoUncheckedIndexedAccessis off.
Questions
How do I type an object with dynamic keys in JSDoc?
@type {Record<string, number>} for string keys and number values. {Object.<string, number>} and {{ [key: string]: number }} are equivalent spellings.
Sources
- Utility Types: Record<Keys, Type> · TypeScript handbook
- JSDoc Reference: Object.<K, V> · TypeScript handbook
- Object Types: index signatures · TypeScript handbook
Last reviewed 20 October 2026.