Introduction: Why is TypeScript Complaining About Your Code?
\n\nHave you ever written TypeScript code that seems perfectly logical, but the compiler suddenly throws a frightening error? If you've encountered the message "An index signature parameter type cannot be a union type. Consider using a mapped object type instead" — you are not alone. This error is one of the most popular questions on Stack Overflow (thousands of views!), and it often stumps even experienced developers.
\n\nIn this article, we won't just show you how to fix the error. We'll break down why TypeScript is so strict, and how to use mapped object types to make your code not only functional but also safer. Let's go!
\n\nWhat is an Index Signature and Why Do You Need It?
\n\nImagine you are creating an object where the keys are strings and the values are numbers. For example, a dictionary of product prices:
\n\n// Regular object\nconst prices = {\n apple: 100,\n banana: 50,\n cherry: 200\n};\n\nBut what if you want to tell TypeScript: "Hey, this object can have any string keys, and all values will be numbers"? That's where the index signature comes in — a special syntax that describes a pattern for all properties of an object:
\n\n// Index signature: [key: string]: number\ninterface PriceMap {\n [key: string]: number;\n}\n\nconst prices: PriceMap = {\n apple: 100,\n banana: 50\n};\n\nHere, [key: string]: number is the index signature. It says: "Keys are strings, values are numbers." Simple, right?
So What's the Problem with Union Types?
\n\nNow imagine you want to restrict the possible keys to just two options: 'name' or 'age'. It seems logical to write this:
// ❌ ERROR! This is not allowed\ninterface Person {\n [key: 'name' | 'age']: string | number;\n}\n\nAnd then TypeScript throws that very error: "An index signature parameter type cannot be a union type. Consider using a mapped object type instead".
\n\nWhy? Because the index signature is meant to describe an unlimited set of keys. If you want to specify a specific list of keys, that's no longer a pattern, but an exact description of the structure. TypeScript doesn't allow mixing these concepts.
\n\nHow to Fix the Error? Use Mapped Object Type
\n\nThe solution that the compiler itself suggests is to use a mapped object type. It sounds complicated, but in reality, it's just a way to "iterate" over all keys from a union type and create a property for each one.
\n\nHere's what it looks like:
\n\n// ✅ CORRECT: using mapped type\ntype Person = {\n [K in 'name' | 'age']: K extends 'name' ? string : number;\n};\n\n// Or a simpler version if the value types are the same:\ntype PersonSimple = {\n [K in 'name' | 'age']: string | number;\n};\n\nLet's break down the syntax [K in 'name' | 'age']:
- \n
K— is a variable that takes each value from the union type one by one ('name', then'age'). \n in— is a keyword that starts a "loop" over the types. \n 'name' | 'age'— is our set of keys. \n
As a result, TypeScript will create an object with two specific properties: name and age. The error is gone!
Example with Different Value Types
\n\nIt's often the case that different keys should have different value types. Using conditional types, this can be solved elegantly:
\n\ntype Config = {\n [K in 'host' | 'port' | 'debug']: \n K extends 'host' ? string :\n K extends 'port' ? number :\n boolean;\n};\n\n// Now: