🔷 TypeScript Q37 / 101

What is index signature?

AI-Powered Answer ✓ Answered

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:

typescript
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.

typescript
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.

typescript
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 to obj['0']. TypeScript enforces that a number index signature is a subtype of a string index signature if both are present.
  • symbol: Less common, but allows for properties keyed by symbol values.

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.

typescript
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 Record or Map-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 string index signature and one number index 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*.