In TypeScript, we have a concept called index signature which is represented by the syntax "{[key: any]: any}". We use it to specify the type of an object to which we might not know the "key" type, the "value" type or both in the "key-value" pair.

Here is an example of an index signature:

  interface UserType {
    [index: string]: any;
  }

  const user: UserType = { name: "Sharooq", age: 23 };

In this example, the "value" of an object can be of any type while the "key" type is always a string.

Different Combinations

Depending on the available information on how the object's shape would be will determine how we configure the index signature.

Let's say we know the "key" type would be a number and the "value" type will be a string. Then we can configure the index signature as follow.

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

  const user: UserType = { 1: "Sharooq", 2: "23" };

Let's try out another example with a more specific index signature having the "key" type as a string and the "value" type as a number. The following code will generate an error:

 interface UserType {
    [index: string]: string;
  }

  const user: UserType = { name: "Sharooq", age: 23 };  //👈 Type 'number' is not assignable to type 'string'.

The above code will result in an error "Type 'number' is not assignable to type 'string'."

error 1

How can we make this index signature work with two different "value" types?.

This is where we can use the union operator. See the syntax of the index signature below.

  interface UserType {
    [index: string]: string | number;
  }

  const user: UserType = { name: "Sharooq", age: 23 };  //👈 error resolved

This time we have specified the "value" type as either a string or a number. If you want to get more specific, you can exactly the type of any specific property. Let's say, we want the "age" property to only have a number value.

  interface UserType {
    [index: string]: string | number;
    age: number;  //👈 age will only accept number
  }

  const user: UserType = { name: "Sharooq", age: 23 }; 

This ensures that the properties of an object which you know exactly of what type to be type checked by TypeScript. However, for this to work, you need to have specified the type inside the index signature. For example, the code written below will throw an error.

  interface UserType {
    [index: string]: string | number;  //👈 type "property" is not specified
    age: number;
    awesome: boolean; 
  }

  const user: UserType = { name: "Sharooq", age: 23, awesome: true };

The above code gave an error "Property 'awesome' of type 'boolean' is not assignable to 'string' index type 'string | number'".

error 2

This is because the index signature doesn't have the type "boolean" in its value. For the above code to work, we need to include "boolean" in the index signature as shown below:

  interface UserType {
    [index: string]: string | number | boolean;  //👈 all types included
    age: number;
    awesome: boolean;
  }

  const user: UserType = { name: "Sharooq", age: 23, awesome: true };

So, make sure to include all the expected types inside the index signature. This is an efficient way to type the properties whose type you know ahead of time.

Bonus Tip - Read-only index signature

You can make an object "read-only" by using the following syntax:

  interface UserType {
    readonly [index: string]: any ;  //👈 add "readonly"
  }

  const user: UserType = { name: "Sharooq", age: "23", awesome: true }; /

What does the "readonly" type do? This will ensure that the object's values cannot be changed by overwriting. A "readonly" type object can only be accessed to read the property values. We will get an error if we try to write to the object like in the following code:

 interface UserType {
    readonly [index: string]: any;
  }

  let user: UserType = { name: "Sharooq", age: "23", awesome: true };

  user.name = "Salaudeen";  //👈 throws error

In the above code, when we try to overwrite the "name" property of the "user" object, we get an error stating "Index signature in type 'UserType' only permits reading.".

error 3

Conclusion

We use the index signature to type an object whose exact shape, type or property names are unknown to us. We can configure the index signature in multiple ways as mentioned above to make it as specific as possible.