Question
In JavaScript, you can programmatically assign a property to an object like this:
const obj = {};
obj.prop = "value";
In TypeScript, this causes an error because the empty object is inferred as type {}:
const obj = {};
obj.prop = "value"; // Error: Property 'prop' does not exist on type '{}'
How can you assign new properties dynamically to an object in TypeScript while keeping the code type-safe?
Short Answer
By the end of this page, you will understand why TypeScript rejects adding unknown properties to {}, and how to solve it using index signatures, Record, optional properties, and type assertions. You will also learn which approach is safest in real projects and when each option makes sense.
Concept
TypeScript checks not only what values your code uses, but also what shape your objects are allowed to have.
When you write:
const obj = {};
TypeScript infers obj as {}, which means "an object with no known properties." After that, this is not allowed:
obj.prop = "value";
because prop is not part of the inferred type.
This is different from plain JavaScript, where objects are open and properties can be added freely at runtime.
Why this matters
TypeScript's restriction helps catch bugs such as:
- misspelled property names
- unexpected object shapes
- assigning the wrong value types
- using properties that were never defined
If you truly want dynamic keys, you need to tell TypeScript that the object is allowed to have them.
Common ways to allow dynamic properties
1. Index signature
Use this when property names are not known ahead of time.
const obj: { [key: string]: string } = {};
obj. = ;
obj. = ;
Mental Model
Think of a TypeScript object type like a form with allowed fields.
{}means the form has no fields.{ prop?: string }means the form has one optional field calledprop.{ [key: string]: string }means the form allows any field name, as long as each value is a string.
JavaScript says, "Add any field you want." TypeScript says, "Tell me what fields are allowed first."
That is the core idea: dynamic behavior is fine, but the type must describe it.
Syntax and Examples
Basic problem
const obj = {};
obj.prop = "value"; // Error
obj is inferred as {}, so no properties can be added unless the type says so.
Solution 1: Index signature
const obj: { [key: string]: string } = {};
obj.prop = "value";
obj.city = "Paris";
This is useful when keys are dynamic.
Solution 2: Record
const settings: Record<string, string> = {};
settings.theme = "dark";
settings.language = "en";
Record<string, string> is equivalent to an index signature for string keys.
Solution 3: Optional known properties
Step by Step Execution
Consider this example:
const scores: Record<string, number> = {};
scores.alice = 10;
scores.bob = 15;
Step by step:
-
const scores: Record<string, number> = {};- Creates an empty object.
- Tells TypeScript that any string key is allowed.
- Tells TypeScript that every value must be a number.
-
scores.alice = 10;aliceis treated as a string key.10is a number, so this is valid.
-
scores.bob = 15;bobis another string key.15is also valid.
-
If you try this:
scores.charlie = "high";
Real World Use Cases
Dynamic object properties are common in real programs.
Configuration maps
const config: Record<string, string> = {};
config.apiUrl = "https://example.com";
config.mode = "production";
Counting items
const counts: Record<string, number> = {};
counts.apple = 3;
counts.orange = 5;
Grouping data by key
const usersById: Record<string, string> = {};
usersById["u1"] = "Alice";
usersById["u2"] = "Bob";
API response containers
const errors: Record<, > = {};
errors. = ;
errors. = ;
Real Codebase Usage
In real codebases, developers usually avoid using plain {} for objects that will grow later. Instead, they choose a type based on how predictable the keys are.
Common patterns
Dictionary objects
const cache: Record<string, string> = {};
Used for lookups, caches, ID maps, and grouped values.
Validation error maps
type ErrorMap = Record<string, string>;
const errors: ErrorMap = {};
Useful for forms where field names map to error messages.
Guarding unknown values
function setValue(obj: Record<string, string>, key: string, value: string) {
obj[key] = value;
}
This makes a helper function reusable and type-safe.
Common Mistakes
1. Using {} and expecting it to behave like JavaScript
Broken code:
const obj = {};
obj.name = "Sam";
Why it fails:
{}does not mean "any object with any properties"- it means "object with no known properties"
Fix:
const obj: Record<string, string> = {};
obj.name = "Sam";
2. Using any too quickly
Broken code:
const obj: any = {};
obj.name = 123;
obj.active = false;
Why it is a problem:
- this removes type safety
- TypeScript can no longer help you catch mistakes
Better:
Comparisons
| Approach | Best for | Example | Notes |
|---|---|---|---|
{} | Empty object with no added properties | const obj = {}; | Too restrictive for dynamic assignment |
| Optional properties | Known fields added later | type T = { name?: string } | Good when keys are known in advance |
| Index signature | Unknown string keys | { [key: string]: string } | Flexible and explicit |
Record<string, T> | Dynamic dictionary objects | Record<string, number> | Cleaner version of an index signature |
Cheat Sheet
Quick reference
Problem
const obj = {};
obj.prop = "value"; // Error
Allow any string key with string values
const obj: Record<string, string> = {};
obj.prop = "value";
Equivalent index signature
const obj: { [key: string]: string } = {};
Known property added later
type T = { prop?: string };
const obj: T = {};
obj.prop = "value";
Variable key
const key = "prop";
const : <, > = {};
obj[key] = ;
FAQ
Why does TypeScript reject obj.prop = "value" on {}?
Because {} is inferred as an object with no known properties. TypeScript only allows properties that exist in the type.
What is the best way to add dynamic properties in TypeScript?
Usually Record<string, T> is the clearest choice when keys are dynamic and values share one type.
Should I use any for dynamic objects?
Usually no. any disables type checking. Prefer Record, index signatures, or a proper interface.
When should I use optional properties instead of Record?
Use optional properties when the property names are known ahead of time but may be assigned later.
Is Record<string, string> the same as { [key: string]: string }?
Yes, for this use case they represent the same idea: any string key with string values.
Can dynamic object properties have different value types?
Yes. Use a union type such as Record<string, string | number | boolean>.
Should I use an object or a Map for dynamic keys?
Mini Project
Description
Build a simple score tracker where player names are used as dynamic object keys. This demonstrates how to safely add properties to an object in TypeScript when the keys are not known in advance.
Goal
Create a TypeScript object that stores player scores using dynamic property assignment with proper typing.
Requirements
Create an empty score object that allows dynamic string keys.
Add at least three player scores programmatically.
Print one individual score and then print the full object.
Ensure all score values are numbers.
Do not use any.
Keep learning
Related questions
Angular formGroup Error Explained: Fixing 'Can't bind to formGroup' in Reactive Forms
Learn why Angular shows 'Can't bind to formGroup' and how to fix it by importing ReactiveFormsModule correctly.
Fix "Element implicitly has an 'any' type" in TypeScript Object Indexing
Learn why TypeScript rejects string object indexing and how to fix it with keyof, unions, and typed object keys in React.
Fix "Property has no initializer" in Angular TypeScript Components
Learn why Angular TypeScript shows "Property has no initializer" and how to fix it using defaults, optional properties, or definite assignment.