Why is this topic blowing up on Stack Overflow?
\n\nIf you've ever written in TypeScript and tried to create an object where all values must be of the same type, you've likely encountered this problem. The question "Enforcing the type of the indexed members of a Typescript object" has garnered thousands of views on Stack Overflow for a reason — it's a classic "gotcha" for developers transitioning from JavaScript.
\n\nImagine: you have a cache dictionary, a configuration with dynamic keys, or simply a map with numbers. You want TypeScript to ensure that every value in the object is a string, number, or some complex type. But the compiler somehow lets errors slip through. Sound familiar?
\n\nIn this article, we'll break down all the main approaches to solving this problem: from simple Index Signatures to advanced Mapped Types. You'll learn when to use each method and what pitfalls await you.
The Problem: an object with dynamic keys
\n\nLet's say we're writing a user management system. We have an object where the keys are user IDs and the values are their names:
\n\ninterface UserCache {\n [userId: string]: string;\n}\n\nconst cache: UserCache = {\n 'user_1': 'Alice',\n 'user_2': 'Bob',\n // 'user_3': 123; // Error! Must be a string\n};\n\ncache['user_4'] = 'Charlie'; // OK\ncache['user_5'] = 42; // Error! Type 'number' is not assignable to 'string'\n\n\nIt seems to work. But there's a nuance: Index Signature allows any string keys. What if we want to restrict the set of allowed keys?
Approach 1: Index Signature — the classic
\n\nIndex Signature is the simplest way to specify the type for all values of an object. Syntax:
\n\ninterface StringMap {\n [key: string]: string;\n}\n\nconst map: StringMap = {\n hello: 'world',\n foo: 'bar',\n};\n\n\nPros:
\n- \n
- Simplicity and readability \n
- Works with any number of keys \n
Cons:
\n- \n
- Cannot restrict the set of keys \n
- All values must be of the same type (or a union) \n
- Does not work well with
undefinedvalues (requires caution) \n
A common beginner mistake: trying to add a method to such an object:
\n\ninterface BadMap {\n [key: string]: string;\n length: number; // Error! 'number' is not assignable to 'string'\n}\n\n\nThis won't work because length violates the common type. If you need to mix types, use a union or Mapped Types.
Approach 2: Record — a built-in helper
\n\nTypeScript provides a built-in type Record<K, V> that does the same thing but with a more concise syntax:
type StringRecord = Record<string, string>;\n\nconst config: StringRecord = {\n apiUrl: 'https://api.example.com',\n timeout: '5000', // OK, string\n // port: 8080, // Error!\n};\n\n// You can restrict keys:\ntype AllowedKeys = 'name' | 'email' | 'phone';\ntype UserRecord = Record<AllowedKeys, string>;\n\nconst user: UserRecord = {\n name: 'Alice',\n email: 'alice@example.com',\n phone: '+1234567890',\n // age: '30', // Error! 'age' is not in AllowedKeys\n};\n\n\nWhen to use:
\n- \n
- When you need to quickly create a typed dictionary \n
- When the keys are a specific set of values (a union) \n
- For configurations with known fields \n
Drawback: Record does not allow specifying different types for different keys. For that, there are Mapped Types.
Approach 3: Map — an ES6 alternative
\n\nIf you use JavaScript's Map, TypeScript types it perfectly