TypeScript Index Signatures

In TypeScript, we usually define our object structures by listing every property name and its type. However, there are times when you don't know the exact names of the properties beforehand such as when you're handling a list of user-generated settings, a cache of data from an API, or a simple dictionary of terms. This is where index signatures come in.

Index signatures allow you to define a "catch-all" for properties that haven't been explicitly named. They provide a way to describe the "shape" of an object's keys and the types of their corresponding values, ensuring your code remains type-safe even when dealing with dynamic data.

Developer Tip: Think of an index signature as a contract that says: "I don't know what the keys will be called, but I guarantee that every key will be a certain type and every value will match a specific format."

 

What is an Index Signature?

An index signature is a special syntax in TypeScript used to represent objects where the property names are unknown at compile time. It acts as a placeholder for any property that might be added to the object later.

You define an index signature using square brackets []. Inside the brackets, you specify a name for the key (this is just a label for readability, usually key or index) and its type (which must be string, number, symbol, or a template literal). Outside the brackets, you specify the type of the value that will be stored under that key.

Syntax

The basic syntax for an index signature looks like this:

interface SomeObject {
  [key: string]: type;
}
  • [key: string]: This tells TypeScript that this object can have any number of properties, and those properties must be strings.
  • type: This represents the data type that every one of those properties must adhere to.
Common Mistake: Beginners often try to use boolean or object as the key type in an index signature. TypeScript only allows string, number, symbol, and template string patterns for keys.

 

Example of Index Signatures

Imagine you are building a language translation system where you map English words to their Spanish equivalents. Since you could have thousands of words, you wouldn't want to define every single one in an interface. Instead, you use an index signature:

interface Dictionary {
  [key: string]: string;
}

const translations: Dictionary = {
  hello: "hola",
  goodbye: "adiós",
  thankYou: "gracias",
};

console.log(translations.hello);   // Output: hola
console.log(translations.thankYou); // Output: gracias

Explanation:

  • The Dictionary interface says: "You can add any property you want, as long as the property name is a string and the value is also a string."
  • If you tried to assign a number to translations.hello, TypeScript would throw a compilation error, protecting you from accidental type mismatches.
Best Practice: If your object is a simple "key-value" pair where all keys and values are the same type, consider using the built-in utility type Record<string, string> as a cleaner alternative to a full interface.

 

Key Concepts of Index Signatures

  1. Flexible Object Types: They are perfect for "bag of properties" objects where the specific keys are determined by external data (like a JSON response).
  2. Key Types: While string is most common, number is used for "Array-like" objects. In JavaScript, all object keys are technically converted to strings, but TypeScript uses the number key type to enforce numeric indexing.
  3. Exhaustiveness: Once an index signature is defined, every other explicitly named property in that interface must match the index signature's value type.

 

Example with number Keys

You might use a numeric index signature when you're building a custom data structure that mimics an array or a list where items are accessed by their position:

interface ResponseList {
  [index: number]: string;
}

const list: ResponseList = ["Success", "Error", "Pending"];

console.log(list[0]); // Output: Success
console.log(list[1]); // Output: Error

Explanation:

  • The ResponseList interface ensures that any numeric key used to access the object will return a string.
  • This is particularly useful when dealing with arguments objects or DOM collections that behave like arrays but aren't actually arrays.

 

Combining Index Signatures with Other Properties

You can mix specific, known properties with an index signature. This is very common in configuration objects where you have a few "required" settings but want to allow users to add their own custom metadata.

interface Product {
  id: number;
  name: string;
  [key: string]: string | number; // Catch-all for extra metadata
}

const product: Product = {
  id: 101,
  name: "Mechanical Keyboard",
  brand: "Logitech", // Dynamic key
  switches: "Brown", // Dynamic key
  stock: 50          // Dynamic key
};

Explanation:

  • The Product interface requires an id (number) and a name (string).
  • The index signature [key: string]: string | number allows any other properties, provided they are either strings or numbers.
Watch Out: If you define an index signature like [key: string]: string, you cannot have a specific property like id: number. TypeScript will complain because id is a string-based key, and its value (number) violates the index signature (string).

 

Limitations of Index Signatures

  1. Value Type Uniformity: As mentioned, all named properties must be subtypes of the index signature's value type. If your index signature is string, you can't have a number property.
  2. Runtime Errors: TypeScript assumes that if you access an index, the value will exist. This can lead to bugs if the key isn't actually present in the object.
Developer Tip: To make your code safer, you can add undefined to the value type: [key: string]: string | undefined;. This forces you to check if the property exists before using it.

 

Example of Restricting Index Signatures

In some cases, you want to use an index signature to ensure that an object only contains a certain type of data, preventing any "polluted" properties from entering your logic.

interface UserPermissions {
  [role: string]: boolean;
}

const permissions: UserPermissions = {
  admin: true,
  editor: false,
  viewer: true,
  // guest: "yes" // Error: Type 'string' is not assignable to type 'boolean'.
};

Explanation:

  • By setting the value type to boolean, we ensure that the permissions object can only store true/false values.
  • This is a great way to create a "Set-like" object where you check for the existence or status of specific roles.

 

Summary

Index signatures are a powerful tool for managing dynamic data in TypeScript. They bridge the gap between strict typing and the flexible, dynamic nature of JavaScript objects.

  • Index Signature: Use [key: T]: V to handle unknown property names while maintaining type safety.
  • Consistency: Remember that named properties must be compatible with the index signature's value type.
  • Flexibility: You can use string or number as keys to model dictionaries, caches, or array-like structures.

Whether you're building a complex state management system or a simple lookup table, index signatures help you write cleaner, more predictable code that scales with your data.