What is index signature?
An index signature in TypeScript allows you to define the type for properties of an object when their names are not known in advance, but their keys and corresponding values follow a consistent pattern. It's a powerful feature for describing 'dictionary-like' or 'map-like' types.
What is an Index Signature?
An index signature specifies the type for properties accessed via an index (like obj['key'] or arr[0]). It declares the type of the key (the index) and the type of the value that corresponds to that key. This is particularly useful when you're working with data where the exact property names are dynamic, but you know the general structure of the keys and their associated values.
Syntax
The basic syntax for an index signature within an interface or type alias is as follows:
interface MyDictionary {
[key: KeyType]: ValueType;
}
Where KeyType can be string, number, or symbol, and ValueType can be any type.
Example: Dictionary-like Objects (String Index Signature)
A common use case is defining an object that acts like a dictionary or a map, where all keys are strings and all values are of a particular type.
interface StringNumberMap {
[key: string]: number;
}
const scores: StringNumberMap = {
'Alice': 95,
'Bob': 88,
'Charlie': 72
// 'David': 'seventy' // Error: Type 'string' is not assignable to type 'number'.
};
console.log(scores['Alice']); // 95
const dynamicKey = 'Eve';
scores[dynamicKey] = 100; // Allowed
// scores.someOtherProperty = true; // Error: Property 'someOtherProperty' of type 'boolean' is not assignable to 'string' index type 'number'.
In this example, StringNumberMap specifies that any property accessed via a string key must have a number value. This allows us to add or access properties dynamically, as long as they conform to this type.
Example: Array-like Objects (Number Index Signature)
You can also define objects where properties are accessed using numeric indices, similar to arrays.
interface NumberStringArray {
[index: number]: string;
length: number; // Often combined with known properties like length
}
const messages: NumberStringArray = {
0: 'Hello',
1: 'World',
length: 2
// 2: 123 // Error: Type 'number' is not assignable to type 'string'.
};
console.log(messages[0]); // 'Hello'
const myArr: NumberStringArray = ['apple', 'banana']; // Works for arrays too
console.log(myArr.length); // 2
Key Type Considerations
string: For properties accessed with string literals or string variables. If an object has both a string and a number index signature, the number index signature's value type must be assignable to the string index signature's value type because JavaScript objects convert numeric property names to strings internally.number: For properties accessed with numeric literals or number variables. In JavaScript,obj[0]is often equivalent toobj['0']. TypeScript enforces that anumberindex signature is a subtype of astringindex signature if both are present.symbol: Less common, but allows for properties keyed bysymbolvalues.
An interface can have at most one string index signature and at most one number index signature. If both are present, the type returned by the numeric index signature must be a subtype of the type returned by the string index signature.
interface MixedIndexSignature {
[key: string]: string | number; // String index signature
[index: number]: number; // Number index signature (must be assignable to string index)
name: string; // Explicit property (must be assignable to string index)
}
const obj: MixedIndexSignature = {
'prop1': 'hello',
0: 123,
'name': 'Mixed Object'
};
// const invalid: MixedIndexSignature = { 'prop2': true }; // Error: boolean not assignable to string | number
Use Cases
- Dynamic Object Properties: When processing data from APIs or user input where property names are not fixed.
- Dictionary/Map Interfaces: Creating types for data structures that map keys to values (e.g., a cache, a lookup table).
- Allowing Additional Properties: To define an object with some known properties, but also allow any number of additional properties of a specific type.
- Typing
RecordorMap-like types: Provides the underlying type safety for such constructs.
Limitations
- Specificity: If an interface has an index signature, any explicitly defined properties must conform to the type of the index signature. For example, if
[key: string]: string;is defined,id: number;would be an error. - Only One of Each Key Type: You can only have one
stringindex signature and onenumberindex signature per type/interface. - Literal Indexing: Index signatures don't prevent you from trying to access properties that don't exist at runtime; they only enforce type safety at compile-time for *indexed access*.