How to control the type of elements in an indexed object in TypeScript

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

Why is this topic blowing up on Stack Overflow?

\n\n

If 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\n

Imagine: 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\n

In 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.

\n\n

The Problem: an object with dynamic keys

\n\n

Let'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\n
interface 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\n

It 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?

\n\n

Approach 1: Index Signature — the classic

\n\n

Index Signature is the simplest way to specify the type for all values of an object. Syntax:

\n\n
interface StringMap {\n  [key: string]: string;\n}\n\nconst map: StringMap = {\n  hello: 'world',\n  foo: 'bar',\n};\n
\n\n

Pros:

\n
    \n
  • Simplicity and readability
  • \n
  • Works with any number of keys
  • \n
\n\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 undefined values (requires caution)
  • \n
\n\n

A common beginner mistake: trying to add a method to such an object:

\n\n
interface BadMap {\n  [key: string]: string;\n  length: number; // Error! 'number' is not assignable to 'string'\n}\n
\n\n

This won't work because length violates the common type. If you need to mix types, use a union or Mapped Types.

\n\n

Approach 2: Record — a built-in helper

\n\n

TypeScript provides a built-in type Record<K, V> that does the same thing but with a more concise syntax:

\n\n
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\n

When 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
\n\n

Drawback: Record does not allow specifying different types for different keys. For that, there are Mapped Types.

\n\n

Approach 3: Map — an ES6 alternative

\n\n

If you use JavaScript's Map, TypeScript types it perfectly

Blogs

Book Recommendations