Index Signatures in TypeScript
An index signature lets you type objects when you do not know the exact property names ahead of time, only the pattern of keys and values. Learn the syntax and when to use it.
TypeScript index signatures describe the type of values you get when you access an object with an unknown key. You use one when you do not know the exact property names ahead of time, but you do know the pattern: every property key is some type, and every property value is some type.
This is common for dictionary-like objects, configuration maps, and any object where keys are dynamic.
String index signatures
The most common index signature uses string keys. It says: for any string key, the value at that key is of a given type.
interface StringMap {
[key: string]: string;
}
const translations: StringMap = {
hello: "Hola",
goodbye: "Adios",
thank_you: "Gracias",
};
console.log(translations["hello"]); // "Hola"The syntax [key: string]: string means that any string key on a StringMap object returns a string value. The key name -- here called key -- is arbitrary. You could call it index, name, or anything else. It is a placeholder that never appears in your code.
String index signatures also enforce that every explicitly named property matches the index type. If you declare a name property of type string alongside a string index of type number, the compiler reports an error:
interface BadDict {
[key: string]: number;
length: number;
name: string;
// Error: 'name' must be number to match index signature
}To allow mixed types across different named properties, widen the index value type to a union. This lets length be a number while name is a string, because the index signature accepts both:
interface MixedDict {
[key: string]: number | string;
length: number;
name: string;
}Number index signatures
A number index signature describes array-like access. When you access the object with a number, you get back a value of the given type.
interface NumberArray { [index: number]: string; }
const arr: NumberArray = ["a", "b", "c"];
console.log(arr[1]); // "b"There is an important rule: the type returned by a number indexer must be a subtype of the type returned by any string indexer on the same interface. This is because JavaScript converts numeric keys to strings when indexing objects. Accessing arr[1] is the same as arr["1"] at runtime.
Template string index signatures
TypeScript also supports index signatures with template string patterns. Use these when keys follow a known prefix.
interface Headers {
"content-type": string;
"content-length": string;
[key: `x-${string}`]: string;
}The pattern x-${string} matches any key starting with "x-". This is useful for HTTP custom headers. Here is how you would read those headers:
function handle(r: Headers) {
console.log(r["content-type"]);
console.log(r["x-powered-by"]); // OK
}An unknown key without the prefix produces an error. This gives you precise control over which dynamic keys are allowed while keeping standard properties fully typed.
Readonly index signatures
Add the readonly modifier to prevent assignment through bracket notation.
interface ReadonlyMap {
readonly [key: string]: number;
}
const scores: ReadonlyMap = { alice: 95, bob: 87 };
scores["alice"] = 100;
// Error: Index signature only permits reading.This makes the entire index immutable at compile time. You can still read any value, but you cannot set new ones or change existing ones through the index.
When to use index signatures
Use an index signature when you need to type an object with unknown or dynamic keys -- dictionaries, lookup tables, configuration maps, or API response objects where the keys vary. The index signature tells TypeScript what type of value to expect for any key, without listing every possible key.
Do not use an index signature when you know the full set of property names. In that case, declare each property explicitly. The explicit declaration gives you better autocomplete, more precise type checking, and clearer error messages.
For more on object types, see object types in TypeScript and interfaces in TypeScript explained.
Common mistakes
Forgetting that named properties must match the index type. If your index signature returns number, every named property must also be number (or a subtype). Use a union in the index signature if you need different types for different named properties.
Using an index signature when you know the keys. If you know every property name, declare them explicitly. Index signatures sacrifice precision for flexibility. Use them only when you need that flexibility.
Rune AI
Key Insights
- An index signature describes the type of values for any key matching a pattern.
- String index signatures are the most common, used for dictionary and config objects.
- Number indexers must return a subtype of the string indexer's type.
- Named properties must be compatible with the index signature's value type.
- Use readonly index signatures to prevent assignment through bracket notation.
Frequently Asked Questions
Can I have both a string and a number index signature?
Can index signatures be readonly?
How do index signatures interact with explicitly named properties?
Conclusion
Index signatures type objects when you know the value pattern but not the exact keys. Use a string index for dictionary-like objects, a number index for array-like access, and template string indexes for prefix or suffix patterns. Remember that named properties must match the index signature's value type.
More in this topic
TypeScript Compiler Explained
The TypeScript compiler (tsc) turns .ts files into .js files while checking types. Learn what it does, how to use it, and how it fits your workflow.
Set Up ESLint for TypeScript
Install and configure ESLint with typescript-eslint to catch bugs and enforce consistent code style in your TypeScript project.
Pick Utility Type in TypeScript
Pick creates a new type by selecting only the properties you name from an existing type. Use it to build focused types for specific views, API responses, or function parameters.