Indexable types allow you to define types where properties are accessed via an index. This can be particularly useful for arrays, dictionaries, or any structure where elements are accessed by keys (indices).

You can define indexable types using the bracket syntax for indices (either number or string) to specify the type of the key, and the type of the corresponding value.

Example 1: Indexable Array Type

interface StringArray {
  [index: number]: string;  // index of type number, value of type string
}
 
let myArray: StringArray;
myArray = ["Bob", "Fred"];
let myStr: string = myArray[0];  // OK: myStr is a string
  • StringArray is an indexable type where the index is a number, and the value at that index is a string.
  • You can access elements like myArray[0], and they will be of type string.

Example 2: Class with Indexable Types

class Animal {
  name: string;
}
 
class Dog extends Animal {
  breed: string;
}
 
// Error: indexing with a numeric string might get you a completely separate type of Animal!
interface NotOkay {
  [x: number]: Animal;  // The index is a number, but value can be an Animal
  [x: string]: Dog;     // The index is a string, but value is a Dog
}
  • This causes a conflict because index signatures with different types (i.e., number and string) are incompatible if they return different types (Animal and Dog).

Example 3: Number Dictionary

interface NumberDictionary {
  [index: string]: number;  // Index type is a string, value type is a number
  length: number;            // length property is valid
  name: string;              // Error: `name` is not a valid property of the dictionary
}
  • NumberDictionary is an indexable type where you can index it with a string and get a number.
  • The name property, however, is not compatible because it doesn’t fit the index signature of the dictionary.

Example 4: Mixed Type Dictionary

interface NumberOrStringDictionary {
  [index: string]: number | string;  // Values can be either a number or a string
  length: number;                    // OK: length is a number
  name: string;                      // OK: name is a string
}
  • NumberOrStringDictionary allows both number and string values for the index, and also defines some specific properties (length and name).

Example 5: Readonly Indexable Types

interface ReadonlyStringArray {
  readonly [index: number]: string;  // Readonly array of strings
}
 
let myArray: ReadonlyStringArray = ["Alice", "Bob"];
myArray[2] = "Mallory";  // Error: Cannot assign to '2' because it is a read-only property
  • ReadonlyStringArray is an indexable type where the elements are readonly.
  • Trying to modify any element of this array (myArray[2] = "Mallory") results in an error.

Key Points:

  1. Index signatures allow an object to be indexed by a type (e.g., string or number).
  2. You can specify what the values of those indexed properties should be (e.g., string or number).
  3. Readonly indexable types make the elements of an array or object immutable, i.e., you cannot modify the elements after they are set.
  4. Conflicts can arise if you define conflicting index signatures for different types (like number and string).