Records & Index Signatures
An index signature ( { [k: string]: V } ) types an object as a dictionary with any number of keys, while Record<K, V> is the built-in utility for an object whose keys are K and values are V — both are how you give types to key-value maps.
Learn Records & Index Signatures in our free TypeScript course — an interactive lesson with runnable examples, a practice exercise and a quick reference.
Part of the free TypeScript course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.
What You'll Learn
💡 Real-World Analogy
Think of a hotel's room-key board. An index signature is a board with unlimited unlabeled hooks: any room number can hang a key (any string key, any value). A Record<union, V> is a board pre-printed with exactly the rooms that exist — every slot is named and must be filled. And noUncheckedIndexedAccess is the honest sign reminding you that reaching for a room that doesn't exist gets you an empty hook (undefined), not a key.
1. Index Signatures: Open Dictionaries
When you don't know the keys in advance — a scores-by-name map, feature flags, a cache — an index signature types it. { [name: string]: number } means "any string key maps to a number." You can read and write any key, iterate with Object.entries , and you can even combine an index signature with a few known fixed properties, as long as their types are compatible.
2. Record<K, V> and keyof
Record<K, V> builds a key-value object type in one expression. With a union of literal keys — Record<"admin" | "user", boolean> — every listed key is required, making it ideal for fixed maps. With Record<string, V> it behaves like an open index signature. The keyof operator pulls the key union back out of any object type.
3. noUncheckedIndexedAccess : Honest Lookups
By default, TypeScript optimistically types a dictionary lookup as the value type — even though, at runtime, a missing key is undefined . That gap causes real "cannot read property of undefined" crashes. The noUncheckedIndexedAccess compiler flag closes it by typing every index access as V | undefined , forcing you to check or default first. It's strongly recommended whenever you work with dictionaries.
🎯 Your Turn
Build a word-count dictionary, defaulting a missing count to 0 . Fill in the blank marked ___ with the right operator, then run it.
Common Errors (and the fix)
- ❌ "Cannot read properties of undefined" from a dictionary lookup. ✅ Enable noUncheckedIndexedAccess and default with ?? fallback before using the value.
- ❌ "Property 'admin' is missing" on a fixed Record<union, V> . ✅ Provide every key in the union, or switch to an open Record<string, V> .
- ❌ Mixing known fields with an incompatible index signature — TS requires the fixed property's type to be assignable to the index value type. ✅ Align the types or split into separate shapes.
- ❌ Iterating with for...in and hitting prototype keys . ✅ Use Object.keys / Object.entries , which only see own enumerable keys.
Pro Tips
- 💡 Use Record<Union, V> for exhaustive maps — a color-per-status or handler-per-event table where every key must be present.
- 💡 Turn on noUncheckedIndexedAccess for any code that reads dictionaries by dynamic keys — it eliminates a whole class of undefined crashes.
- 💡 Derive key unions with keyof typeof obj so a function only accepts keys that actually exist on a specific object.
- 💡 Reach for Map when keys aren't strings, order matters, or you delete entries often — objects shine for static, string-keyed data.
📚 Keep learning
- TypeScript Handbook — Index Signatures
- TypeScript Handbook — Record<Keys, Type>
- TSConfig Reference — noUncheckedIndexedAccess
Frequently Asked Questions
Mini-Challenge: groupBy
No blanks this time — just a brief and a starting outline. Build the grouping function yourself, run it, and check your output against the example in the comments.
🎉 Lesson Complete
- ✅ An index signature { [k: string]: V } types an open dictionary
- ✅ Record<K, V> builds key-value object types concisely
- ✅ Record<union, V> requires all keys; Record<string, V> is open
- ✅ keyof extracts the union of an object type's keys
- ✅ noUncheckedIndexedAccess makes lookups V | undefined — handle the gap
- ✅ Next lesson: Template Literal Types — string types built from other types
Practice quiz
What does the index signature '{ [name: string]: number }' describe?
- An object with exactly one key
- An array of numbers
- An object where any string key maps to a number
- A number named 'name'
Answer: An object where any string key maps to a number. An index signature types an open dictionary: any string key, all values numbers.
What does Record<'admin' | 'user', boolean> require?
- Exactly the keys admin and user, both present
- Any string keys
- At most two keys
- Only the admin key
Answer: Exactly the keys admin and user, both present. A Record over a union of literal keys requires every listed key to be present.
How does Record<string, number> compare to an index signature?
- It is stricter
- It requires all keys
- It forbids dot access
- It behaves like an open index signature - any string key
Answer: It behaves like an open index signature - any string key. Record<string, V> is an open dictionary, just like { [k: string]: V }.
What does 'keyof' produce when applied to an object type?
- The value types
- A union of the object's keys
- The number of keys
- A new object
Answer: A union of the object's keys. keyof Roles for Record<'admin'|'user', boolean> gives 'admin' | 'user'.
With noUncheckedIndexedAccess on, what is the type of a dictionary lookup?
- V | undefined
- V
- unknown
- never
Answer: V | undefined. The flag makes index access return V | undefined, matching runtime reality.
At runtime, looking up a missing key in an object returns what?
- null
- an error
- undefined
- an empty object
Answer: undefined. A missing key is always undefined at runtime; the flag makes the type honest.
What is a safe way to read a possibly-missing dictionary value?
Use ?? to provide a default when the key is absent.
Can you combine a known property with an index signature?
- No, never
- Only for number keys
- Only inside a class
- Yes, if the known property's type is compatible with the index value type
Answer: Yes, if the known property's type is compatible with the index value type. A fixed property is allowed as long as it's assignable to the index signature's value type.
When should you prefer a Map over a typed object dictionary?
- Always
- When keys aren't strings, order matters, or you delete entries often
- For JSON config
- For string-keyed lookup tables
Answer: When keys aren't strings, order matters, or you delete entries often. Map shines for non-string keys, ordering, frequent inserts/deletes, and a clean size/iteration API.
Which key types are allowed in an index signature?
- Only string
- Only number
- string, number, or symbol
- Any type
Answer: string, number, or symbol. Index signature keys can be string, number, or symbol.
Continue this course
- Previous: readonly & as const
- Next: Template Literal Types