TYPESCRIPT
TypeScript Index Signature: Syntax, Usage, and Examples
An index signature in TypeScript lets you define types for dynamically named object properties. It provides a way to enforce type safety when working with objects where the property names are unknown ahead of time. This is useful when handling key-value structures like dictionaries or maps.
How to Use Index Signatures in TypeScript
The syntax for defining an index signature follows this pattern:
type ObjectType = {
[key: string]: number;
};
This defines an object type where all property names are strings, and their values must be numbers. You can then use this type to create objects:
const scores: ObjectType = {
Alice: 85,
Bob: 92,
Charlie: 78,
};
console.log(scores["Alice"]); // 85
Defining Index Signatures in Interfaces
You can also use an index signature inside an interface:
interface UserAges {
[name: string]: number;
}
const userAges: UserAges = {
Alice: 30,
Bob: 25,
};
console.log(userAges.Bob); // 25
Using a More Specific Key Type
If you need to restrict the keys to a set of predefined strings, use a union type:
type Statuses = "active" | "inactive" | "pending";
interface StatusMap {
[key in Statuses]: boolean;
}
const userStatus: StatusMap = {
active: true,
inactive: false,
pending: true,
};
console.log(userStatus.pending); // true
This ensures that only "active"
, "inactive"
, or "pending"
can be used as keys.
When to Use Index Signatures in TypeScript
Use an index signature when you need a flexible object structure with unknown or dynamic property names. They are particularly useful in cases like:
- Dynamic key-value storage: When working with API responses, logs, or user-generated data, where property names are not predefined.
- Dictionaries and lookup tables: If you need to store a set of related values, such as user permissions or configuration settings.
- Mapping enums or predefined values: If you want to enforce a specific set of keys but allow flexibility in their values.
Index signatures keep TypeScript's type safety while allowing object keys to vary.
Examples of Index Signatures in TypeScript
Defining a Dynamic Object
A common use case for an index signature is when handling an object with arbitrary properties. Here’s an example:
interface Inventory {
[itemName: string]: number;
}
const storeInventory: Inventory = {
apples: 50,
bananas: 30,
oranges: 20,
};
console.log(storeInventory.bananas); // 30
Using Index Signatures in a Class
You can also use an index signature inside a class to store dynamic properties:
class Settings {
[key: string]: string;
constructor() {
this.theme = "dark";
}
}
const userSettings = new Settings();
userSettings.language = "English";
console.log(userSettings.language); // "English"
This approach allows settings to be added dynamically while keeping type safety.
Combining Index Signatures with Other Properties
You can mix explicitly defined properties with an index signature:
interface User {
id: number;
name: string;
[extra: string]: string | number;
}
const user: User = {
id: 1,
name: "Alice",
age: 30, // Allowed due to the index signature
country: "USA",
};
console.log(user.age); // 30
The object still enforces id
and name
while allowing additional properties of type string | number
.
Enforcing a Read-Only Index Signature
If you need an immutable object, use readonly
with the index signature:
interface ReadonlyConfig {
readonly [key: string]: string;
}
const config: ReadonlyConfig = {
theme: "light",
language: "English",
};
// config.theme = "dark"; // Error: Cannot assign to 'theme' because it is a read-only property
This ensures the properties cannot be modified after assignment.
Learn More About Index Signatures in TypeScript
Optional Index Signatures
By default, index signatures require every key to have a corresponding value. If you want to allow undefined values, use the optional modifier:
interface OptionalIndex {
[key: string]: string | undefined;
}
const userProfile: OptionalIndex = {
username: "johndoe",
email: undefined, // Allowed
};
Using Record<T, U>
Instead of Index Signatures
TypeScript provides a built-in utility type called Record<T, U>
that works similarly to an index signature:
type UserRoles = Record<string, string>;
const roles: UserRoles = {
admin: "Full Access",
editor: "Edit Access",
};
console.log(roles.admin); // "Full Access"
Record<string, string>
is equivalent to { [key: string]: string }
but is more concise.
Restricting Key Types in Index Signatures
To prevent invalid keys, use keyof
with an index signature:
type RoleKeys = "admin" | "editor" | "viewer";
interface RoleMap {
[key in RoleKeys]: string;
}
const userRoles: RoleMap = {
admin: "Manage everything",
editor: "Edit content",
viewer: "Read-only",
};
This approach enforces that only "admin"
, "editor"
, and "viewer"
can be used as keys.
Handling Index Signatures in Interfaces vs. Classes
TypeScript handles index signatures slightly differently in interfaces and classes. When using a class, you may need to define an explicit index signature:
class UserConfig {
[key: string]: string;
constructor(public name: string) {
this.theme = "dark";
}
}
const settings = new UserConfig("Alice");
settings.language = "English";
console.log(settings.language); // "English"
Avoiding Index Signature Errors
If an object does not define an index signature but is used as one, TypeScript will throw an error:
interface UserInfo {
name: string;
}
const userInfo: UserInfo = { name: "Bob" };
// console.log(userInfo["age"]); // Error: Property 'age' does not exist on type 'UserInfo'
To fix this, add an explicit index signature or use type assertion:
console.log((userInfo as any)["age"]); // No error, but loses type safety
Index signatures give you the flexibility to define dynamic objects while maintaining TypeScript's type safety. You can use them to create dictionaries, enforce specific key patterns, and build scalable, reusable types.
Sign up or download Mimo from the App Store or Google Play to enhance your programming skills and prepare for a career in tech.