Topics

Jump to a topic.

Search questions

Find a question by title, tag, or summary.

What can be a property key, and in what order are keys listed?

Questions in this chapter (7)
  • Draft
  • medium
  • Askedsometimes
  • Verified October 2026

Answer

What can be a key. The spec defines a property key as a String or a Symbol. Every other value used in obj[expr] or as a computed key goes through ToPropertyKey: ToPrimitive with string preference, then ToString unless the result is a Symbol. So:

  • Numbers become strings: obj[1], obj['1'] and obj[1.0] are one property; -0 becomes '0'.
  • Objects become whatever their toString returns, usually '[object Object]', so two different object keys collide.
  • A Symbol is kept as is, and is unique. Symbol('a') !== Symbol('a'). Symbol.for('a') is shared through the global registry.
  • Symbol keys are skipped by for…in, Object.keys, Object.entries and JSON.stringify; Object.getOwnPropertySymbols and Reflect.ownKeys see them.

Order. OrdinaryOwnPropertyKeys lists an ordinary object's own keys in three groups:

  1. Array indices (canonical numeric strings from '0' to '4294967294') in ascending numeric order.
  2. Other strings in the order the properties were created.
  3. Symbols in the order they were created.

Reflect.ownKeys, Object.keys, Object.values, Object.entries, Object.assign, spread and JSON.stringify all follow it, because they all start from [[OwnPropertyKeys]]. '01', '-1' and '1.5' are not array indices, so they sort by creation. Deleting and re-adding a key moves it to the end of its group.

When you need arbitrary keys of any type, or insertion order that ignores integer-looking keys, use a Map.

Example

const id = Symbol('id');
const user = { name: 'Ada', 2: 'b', [id]: 7, age: 36, 1: 'a', '01': 'c' };
user.role = 'admin';

console.log(Reflect.ownKeys(user)); // → [ '1', '2', 'name', 'age', '01', 'role', Symbol(id) ]
console.log(Object.keys(user)); // → [ '1', '2', 'name', 'age', '01', 'role' ]
console.log(JSON.stringify(user)); // → {"1":"a","2":"b","name":"Ada","age":36,"01":"c","role":"admin"}

const bag = {};
bag[1] = 'number';
bag['1'] = 'string';
bag[{}] = 'object';
console.log(bag); // → { '1': 'string', '[object Object]': 'object' }

Pitfalls

  • Using an object as a key and expecting identity. All plain objects stringify to '[object Object]' and overwrite each other; use a Map or WeakMap.
  • Relying on insertion order for integer-like keys such as IDs. { b: 1, 10: 1, 9: 1 } lists '9', '10', then 'b'.
  • Expecting symbols to be hidden. They are skipped by the common enumeration helpers but are not private; Reflect.ownKeys and Object.getOwnPropertySymbols list them.
  • Assuming a literal { __proto__: x } creates a key. It sets the prototype; { ['__proto__']: x } and JSON.parse('{"__proto__":1}') create an own property.

Follow-ups

Is the order guaranteed for for…in too?
Yes, for ordinary objects. The spec's EnumerateObjectProperties must behave like an informative generator built on Reflect.ownKeys (strings only, own keys first, then the prototype chain), as long as the object isn't mutated mid-loop. The chain walk is covered in the enumeration question.
What about 4294967295 and larger integers?
Array indices stop at 2**32 - 2. In Node 24, Object.keys({ 4294967295: 1, z: 1, 5: 1 }) gives ['5', '4294967295', 'z']: the larger number is an ordinary string and sorts by creation.
Does a Map have these rules?
No. A Map keeps pure insertion order and uses SameValueZero, so 1 and '1' are different keys and any value can be a key.

Exercises

Predict the key list

const o = { b: 1, 3: 1, a: 1, 1: 1 };
delete o.b;
o.b = 1;
console.log(Object.keys(o).join());