Dynamic Object Keys Explained in JavaScript

Posted by

How to use computed property names, symbols, and expressions as object keys — safely and effectively in real-world code.

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, Symbols, 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

Symbols 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(); use Object.getOwnPropertySymbols.
  • JSON: Symbols are lost on serialization.
  • Prototype pollution: Be careful merging untrusted keys like "__proto__"—they can mutate Object.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

Your email address will not be published. Required fields are marked *