Index Signatures & Record
Not every object has a fixed list of properties. Dictionaries, lookup tables and caches have keys you don't know in advance. Index signatures and the Record type describe objects like these: "any key of this type, mapping to values of that type".
Index signatures
interface WordCounts { [word: string]: number; } const counts: WordCounts = {}; for (const w of "the cat saw the dog".split(" ")) { counts[w] = (counts[w] ?? 0) + 1; } console.log(counts); counts.the = "many";
error TS2322: Type 'string' is not assignable to type 'number'.
[word: string]: number means any string key is allowed, and every value must be a number. The name word is only a label for readability. Remove the last line and the program prints { the: 2, cat: 1, saw: 1, dog: 1 }.
Missing keys
An index signature says every string key exists, which isn't really true:
const stock: { [sku: string]: number } = { "A-1": 4 }; const qty = stock["Z-9"]; console.log(qty);
undefined
qty is typed number but is actually undefined. Turning on noUncheckedIndexedAccess in tsconfig.json makes such reads return number | undefined, which forces a check. For lookups where keys are truly dynamic, a Map is often clearer, since map.get() always returns V | undefined.
Mixing fixed properties and an index signature
An interface can have both, but every fixed property must also match the index signature's value type:
interface Headers { [name: string]: string; "content-type": string; "content-length": number; }
error TS2411: Property '"content-length"' of type 'number' is not assignable to 'string' index type 'string'.
The fix is to widen the index signature (string | number) or keep the dynamic part in a separate nested property.
Number and template-pattern keys
Index signature keys can be string, number, symbol, or a template literal pattern:
interface Grid { [row: number]: string; } interface DataAttrs { [attr: `data-${string}`]: string; } const grid: Grid = ["#..", ".#.", "..#"]; const attrs: DataAttrs = { "data-id": "42", "data-role": "tab" }; console.log(grid[1], attrs["data-role"]);
.#. tab
The DataAttrs pattern accepts any key starting with data- and rejects anything else. Template literal types are covered in lesson 30.
The Record utility type
Record<Keys, Value> is a built-in shorthand that's usually easier to read than an index signature:
const capitals: Record<string, string> = { France: "Paris", Japan: "Tokyo", }; capitals.Kenya = "Nairobi"; console.log(Object.keys(capitals).length);
3
Record<string, string> is equivalent to { [key: string]: string }.
Record with a fixed set of keys
Record really shines when the keys are a union of literals. Then every key is required — none can be forgotten:
type Status = "draft" | "review" | "published"; const labels: Record<Status, string> = { draft: "Draft", review: "In review", };
error TS2741: Property 'published' is missing in type '{ draft: string; review: string; }' but required in type 'Record<Status, string>'.This is perfect for lookup tables keyed by an enum or union: add a new status and the compiler shows every table that needs a new entry. Wrap it in Partial<…> if some keys are allowed to be missing.
Iterating safely
Object.keys() always returns string[], even for a Record with literal keys, because objects can have extra properties at runtime. When you know the keys are exactly the ones in the type, you can use Object.entries or assert the key type:
type Status = "draft" | "review" | "published"; const counts: Record<Status, number> = { draft: 4, review: 1, published: 9 }; for (const [status, n] of Object.entries(counts)) { console.log(`${status}: ${n}`); } const keys = Object.keys(counts) as Status[]; console.log(keys.map(k => counts[k]).reduce((a, b) => a + b));
draft: 4 review: 1 published: 9 14
Choosing between them
| Need | Use |
|---|---|
| Fixed, known properties | An interface or type alias |
| A table with a known set of keys | Record<"a" | "b", V> |
| Arbitrary string keys | Record<string, V> or an index signature |
| Frequent adds/removes, non-string keys, guaranteed key order | Map<K, V> |