Suggest an editImprove this articleRefine the answer for “Index signatures and index access types in TypeScript”. Your changes go to moderation before they’re published.Approval requiredContentWhat you’re changing🇺🇸EN🇺🇦UAPreviewTitle (EN)Short answer (EN)**TypeScript index signatures** define types for objects with dynamic string or number keys using `[key: string]: T` syntax. Index access types (`T[K]`) extract the type of a specific property from an existing type. ```typescript interface Dict { [key: string]: number } // index signature type User = { id: string; name: string }; type UserId = User["id"]; // string - index access type AllValues = User[keyof User]; // string ``` **Key point:** signatures shape runtime-writable objects; access types are compile-time-only lookups that produce no JavaScript output.Shown above the full answer for quick recall.Answer (EN)Image**Index signatures** let you type objects with unknown string or number keys. Index access types (`T[K]`) extract a specific property's type from an existing type definition at compile time. Two different tools, two different jobs. ## Theory ### TL;DR - Index signature (`[key: string]: T`) = a shape contract for objects with arbitrary keys - Index access type (`T["key"]`) = reading a property's type from another type - Analogy: a signature is a hotel keycard policy (any guest key opens a room of the same category); an access type is reading the room category from a floor plan - Signatures affect what you can write at runtime; access types produce zero runtime code - `Record<string, T>` and `[key: string]: T` are structurally identical; `Record` is shorter ### Quick Example ```typescript // Index signature: any string key maps to a string value interface StringDict { [key: string]: string; } const dict: StringDict = { a: "apple" }; dict["b"] = "banana"; // OK, dynamic write // Index access type: reads the type of key "a" type Fruit = { a: "apple"; b: "banana" }; type AppleType = Fruit["a"]; // "apple" type AllValues = Fruit[keyof Fruit]; // "apple" | "banana" ``` Signatures let you write dynamic keys at runtime. Access types resolve during type checking and disappear entirely from the compiled JavaScript. ### Key Difference Index signatures define a contract: every value stored under any key must match the declared type, and TypeScript enforces this during assignment. Index access types do nothing at runtime. `User["email"]` is a type-level operation that resolves to whatever type `email` holds in `User`. You can chain them freely: `User["address"]["city"]` drills two levels into a nested type without touching any runtime value. ### When to Use - Config or settings objects with user-defined keys: `[key: string]: unknown` - Pure string-to-value dictionaries: `Record<string, T>` (easier to read than a raw signature) - Extracting a prop type for a generic function: `T[K]` - Getting all value types from an interface: `T[keyof T]` - Typing elements of a `const` array: `typeof arr[number]` - Objects with only known properties and no dynamic keys: skip signatures entirely ### How the Compiler Handles This TypeScript applies excess property checking on fresh object literals. Assigning `{ a: 1, b: "oops" }` to `[key: string]: number` fails immediately because `"oops"` is not a number. After assignment through a variable (structural check), TypeScript widens unknown keys to the signature type without complaint. Index access types go through the type mapper: `T[K]` is substituted during constraint solving, with no emitted code. Both features are fully erased in the JavaScript output. One thing that comes up repeatedly in production code: mixing known properties with a string index signature forces all declared props to satisfy the signature type. This usually pushes teams toward `Record` for pure dictionaries and separate plain interfaces for structured data. ### Common Mistakes **Known property conflicts with the index signature** ```typescript interface Config { name: string; version: number; // Error: number is not assignable to string [key: string]: string; } ``` Every declared property must satisfy the signature. Fix by widening the signature type: ```typescript interface Config { name: string; version: number; [key: string]: string | number; // now compatible } ``` **Using `T[string]` instead of `T[keyof T]`** ```typescript type Obj = { a: 1; b: 2 }; type Bad = Obj[string]; // Error: 'string' is not a valid index type type Good = Obj[keyof Obj]; // 1 | 2 ``` `string` is too wide. Use `keyof Obj` or a specific literal union. **Writing to a `readonly` index signature** ```typescript interface ReadDict { readonly [key: string]: string; } const d: ReadDict = { a: "ok" }; d["b"] = "no"; // Error: index signature only permits reading ``` Drop `readonly` if you need mutation. Keep it only when immutability is intentional. **Forgetting `as const` with array index access** ```typescript const roles = ["admin", "editor"]; // string[] type Role = typeof roles[number]; // string (too wide) const rolesConst = ["admin", "editor"] as const; type RoleConst = typeof rolesConst[number]; // "admin" | "editor" ``` Without `as const`, TypeScript infers `string[]` and the index access gives back `string` instead of a literal union. ### Real-world Usage - React: `React.CSSProperties[keyof React.CSSProperties]` extracts all possible style value types - Express: route params typed as `Record<string, string>` in `req.params` - Redux: `Action["payload"]` extracts the payload type for a specific action variant - Lodash: `Dictionary<T>` is `[key: string]: T` internally - Node.js: `process.env` is typed as `{ [key: string]: string | undefined }` ### Follow-up Questions **Q:** What is the difference between `[key: string]: T` and `Record<string, T>`? **A:** Structurally identical. `Record<string, T>` expands to the same index signature. Most teams prefer `Record` for readability since it signals intent without the bracket syntax. **Q:** Why does `keyof` on a string-indexed interface return `string | number`? **A:** JavaScript coerces numeric keys to strings internally, so TypeScript includes `number` to cover the case where `obj[0]` is equivalent to `obj["0"]`. **Q:** Can you chain index access types? **A:** Yes. `User["address"]["city"]` resolves `address` first, then looks up `city` on that result. Works for any depth as long as each step is a valid key. **Q:** How does `typeof arr[number]` work with `as const` arrays? **A:** `as const` turns the array into a readonly tuple with literal element types. Indexing with `number` produces a union of all those literal types. **Q:** Write a type-safe `get` function using index access types. **A:** ```typescript function get<T, K extends keyof T>(obj: T, key: K): T[K] { return obj[key]; } const user = { id: "1", name: "Alice" }; const name = get(user, "name"); // inferred as string ``` `K extends keyof T` constrains the key to valid ones, and `T[K]` preserves the exact return type without any type assertion. ## Examples ### Basic: Typed Dictionary ```typescript interface Translations { [key: string]: string; } const i18n: Translations = { hello: "hi", goodbye: "bye", }; const greeting = i18n["hello"]; // type: string, value: "hi" const missing = i18n["nope"]; // type: string, actual value: undefined ``` TypeScript cannot know which keys exist at runtime. If you want the type to reflect possible `undefined`, change the signature to `[key: string]: string | undefined`. ### Intermediate: API Endpoint Typing ```typescript interface User { id: string; name: string } interface Post { id: number; title: string } interface ApiResponses { "/users": { users: User[] }; "/posts": { posts: Post[] }; } async function fetchData<T extends keyof ApiResponses>( endpoint: T ): Promise<ApiResponses[T]> { const res = await fetch(endpoint); return res.json(); } const data = await fetchData("/users"); // TypeScript infers: data is { users: User[] } // Change endpoint to "/posts" and the return type changes with it ``` The index access `ApiResponses[T]` ties the return type directly to the endpoint argument. No type assertion needed anywhere. ### Advanced: Extracting Nested Types ```typescript interface Theme { colors: { primary: string; secondary: string; }; spacing: { sm: number; md: number; lg: number; }; } type ThemeColors = Theme["colors"]; // { primary: string; secondary: string } type SpacingKeys = keyof Theme["spacing"]; // "sm" | "md" | "lg" type SpacingValue = Theme["spacing"][SpacingKeys]; // number // Generic helper to extract any section type Section<T, K extends keyof T> = T[K]; type ColorSection = Section<Theme, "colors">; // same as Theme["colors"] ``` Chaining index access types avoids duplicating interface definitions. In design system code, you see this pattern constantly when building typed theme utilities.For the reviewerNote to the moderator (optional)Visible only to the moderator. Helps review go faster.