How to use computed property names, symbols, and expressions as object keys — safely and effectively in real-world code.
Introduction
Have you ever needed to create an object where the key comes from a variable instead of being hardcoded?
const key = "status";
const obj = { [key]: "active" };
console.log(obj); // { status: "active" }
That’s dynamic object keys — a feature introduced in ES6 with computed property names. They allow you to generate object properties at runtime instead of defining them statically.
This is useful when building APIs, merging configs, creating lookup maps, or working with dynamic data (e.g., translations, forms, feature flags).
In this post, we’ll explore all the tricks with dynamic keys — from basic variables to complex expressions, Symbol
s, and TypeScript-safe patterns.
1. The Basics: Computed Property Names
Before ES6, you had to use bracket notation:
const key = "name";
const obj = {};
obj[key] = "Ali";
With ES6+, you can do it inline:
const key = "name";
const obj = { [key]: "Ali" };
console.log(obj); // { name: "Ali" }
👉 Why: Cleaner, especially when constructing objects in one go.
2. Dynamic Keys with Expressions
The brackets allow any valid JS expression.
const prefix = "user";
const obj = {
[`${prefix}_id`]: 42,
[1 + 2]: "three"
};
console.log(obj); // { user_id: 42, '3': 'three' }
- Keys are coerced to strings (unless they’re symbols).
- Numbers become string keys:
"3"
.
3. Using Symbols as Dynamic Keys
Symbol
s are often used for “hidden” or unique keys.
const id = Symbol("id");
const obj = { [id]: 123 };
console.log(obj[id]); // 123
console.log(Object.keys(obj)); // [] (symbols not enumerable by default)
👉 Useful for internal metadata you don’t want to clash with user-defined keys.
4. Dynamic Keys in Object Spread & Merge
When merging with spread, you can insert dynamic keys inline:
const lang = "en";
const translations = {
[lang]: { hello: "Hello" },
es: { hello: "Hola" }
};
console.log(translations.en.hello); // "Hello"
👉 Great for i18n dictionaries, feature flags, or user preferences.
5. Dynamic Keys in Functions
Sometimes you want to build an object return value with keys based on params.
function makeUser(id, key, value) {
return { id, [key]: value };
}
console.log(makeUser(1, "name", "Umar"));
// { id: 1, name: 'Umar' }
👉 Eliminates boilerplate obj[key] = value
.
6. Nested Dynamic Keys
Dynamic keys also work deep in objects:
const field = "email";
const user = {
profile: {
[field]: "test@example.com"
}
};
console.log(user.profile.email); // "test@example.com"
👉 Handy for building API payloads from dynamic form data.
7. Real-World Use Cases
A) Feature Flags
const feature = "darkMode";
const flags = { [feature]: true };
B) Translation Dictionaries
const locale = "fr";
const messages = { [locale]: { greeting: "Bonjour" } };
C) API Query Builders
function where(field, value) {
return { [field]: value };
}
console.log(where("status", "active")); // { status: "active" }
D) Redux/State Updates
function updateField(state, key, value) {
return { ...state, [key]: value };
}
8. TypeScript + Dynamic Keys
TypeScript can infer types with computed keys if you declare them correctly.
A) Simple dynamic key
type User = { id: number; name: string };
function update<T extends object, K extends keyof T>(obj: T, key: K, value: T[K]): T {
return { ...obj, [key]: value };
}
const user: User = { id: 1, name: "Ali" };
const updated = update(user, "name", "Laiba"); // ✅ type-safe
B) Mapping with generics
function mapByKey<T extends Record<string, any>, K extends keyof T>(
arr: T[],
key: K
): Record<string, T> {
return arr.reduce((acc, item) => {
acc[item[key]] = item;
return acc;
}, {} as Record<string, T>);
}
👉 Type safety prevents typos and ensures correct value types.
9. Edge Cases & Gotchas
- Overwriting keys: Later dynamic keys overwrite earlier ones.
- Numbers become strings:
{ [1 + 2]: "x" }
→ key"3"
. - Symbols: Not included in
Object.keys()
; useObject.getOwnPropertySymbols
. - JSON: Symbols are lost on serialization.
- Prototype pollution: Be careful merging untrusted keys like
"__proto__"
—they can mutateObject.prototype
.
Safe merge tip:
const BLOCKED = ["__proto__", "constructor", "prototype"];
function safeAssign(obj, key, value) {
if (BLOCKED.includes(key)) return;
obj[key] = value;
}
10. Performance Considerations
- Dynamic keys are as fast as normal ones for most use cases.
- For huge loops, prefer
Map
if you need arbitrary/dynamic keys (especially non-string). - Symbols add safety but may cost a tiny bit in memory overhead.
Conclusion
Dynamic object keys let you build objects programmatically — from variable names, expressions, symbols, or function parameters. They make APIs more flexible, reduce boilerplate, and enable patterns like config merging, i18n, and state updates.
- Use computed property names (
[expr]
) for inline keys. - Use Symbols for unique/private keys.
- Use TypeScript generics to keep type safety when updating by key.
- Guard against prototype pollution when merging untrusted keys.
Pro Tip: When keys aren’t strings, or when you need fast dynamic lookups, consider using Map
instead of objects—it avoids most of the quirks of dynamic object keys.
Call to Action (CTA)
What’s the coolest or most practical way you’ve used dynamic keys?
Drop a snippet in the comments — and share this with a teammate who still writes obj[key] = value
everywhere.
Leave a Reply