Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

JavaScript (ES2026)  Module 5 – Objects in depth

Object.keys, entries, fromEntries and key order

Turn JavaScript objects into arrays of keys, values and entries, build them back with Object.fromEntries, and predict the order keys come out in.

  • Beginner
  • 15 minutes
  • Examples run with Node.js 24.21.0 and quickjs 0.32.0
  • By MySmartCoPilot
Free lesson, Pro answers: 15 minutes
Free: the whole lesson. With a pass: the model answers to its 2 interview questions.

What you will learn

  • Convert objects to arrays of keys, values or entries and back
  • Transform objects with entries, map and fromEntries
  • Predict the order of an object's properties

Before you start

What you’ll learn · Before you start

What you will learn

  • Convert objects to arrays of keys, values or entries and back
  • Transform objects with entries, map and fromEntries
  • Predict the order of an object's properties

Before you start

On this page

Objects are not arrays, so they have no map, filter or length. Three functions bridge the gap: Object.keys(obj) returns an array of the object’s keys, Object.values(obj) an array of its values and Object.entries(obj) an array of [key, value] pairs. All three list only the object’s own, enumerable, string keys, never inherited or symbol keys. Object.fromEntries(pairs) goes the other way and builds an object from pairs, so entries, an array method and fromEntries together transform a whole object in one expression. The keys always come out in a fixed order that surprises many people: integer-like keys first, smallest first, then the other string keys in the order they were added, then symbols. So Object.keys({ b: 1, 2: 1, a: 1, 1: 1 }) is ['1', '2', 'b', 'a']. This lesson converts a menu object both ways, raises every price in one line, spells out the ordering rule with its edge cases, and shows why numeric ids in an object lose their order.

Keys, values and entries

A menu as keys, values and entries JavaScript · keys-values-entries.mjs
// Object.keys, Object.values and Object.entries turn an object into an array you can loop over or transform.
const menu = { dosa: 80, idli: 40, vada: 35, filterCoffee: 30 };

console.log(Object.keys(menu));
console.log(Object.values(menu));
console.log(Object.entries(menu));
console.log('dishes:', Object.keys(menu).length);

for (const [dish, price] of Object.entries(menu)) {
  if (price >= 40) console.log(`${dish} costs ${price}`);
}
console.log('one of each:', Object.values(menu).reduce((sum, price) => sum + price, 0));

// Only own, enumerable, string keys: inherited properties and symbol keys are left out.
const todaysMenu = Object.create(menu); // inherits every dish of menu
todaysMenu.pongal = 60;
todaysMenu[Symbol('note')] = 'chef special';
console.log(Object.entries(todaysMenu));

Output

[ 'dosa', 'idli', 'vada', 'filterCoffee' ]
[ 80, 40, 35, 30 ]
[
  [ 'dosa', 80 ],
  [ 'idli', 40 ],
  [ 'vada', 35 ],
  [ 'filterCoffee', 30 ]
]
dishes: 4
dosa costs 80
idli costs 40
one of each: 185
[ [ 'pongal', 60 ] ]

Recorded with Node.js 24.21.0 on macOS 26 arm64. To run it yourself: mise exec node@24.21.0 -- node keys-values-entries.mjs

  • Object.keys(menu) gives the dish names and Object.values(menu) the prices, in matching order.
  • Object.entries(menu) gives one [dish, price] pair per property, which is the most useful of the three: with destructuring, for (const [dish, price] of Object.entries(menu)) reads both at once.
  • Object.keys(menu).length is the usual way to count an object’s properties, because objects have no length.
  • todaysMenu inherits every dish from menu and has one of its own, pongal, plus a symbol key. Object.entries lists only pongal: inherited keys and symbol keys are left out. That is usually what you want when an object holds data, and it is the difference from a for...in loop, which also visits inherited enumerable keys.

These functions take a snapshot. Adding or removing keys afterwards does not change an array you already have, and changing the array does not change the object, although a value that is itself an object is still shared by both.

Back to an object with Object.fromEntries

Object.fromEntries takes any iterable of [key, value] pairs, such as the array from Object.entries or a Map, and builds a new plain object from it. That makes the round trip entries → array methods → fromEntries the standard way to transform an object:

Raise, filter and swap a menu's prices JavaScript · price-update.mjs
// Transforming an object: Object.entries, then array methods, then Object.fromEntries.
const menu = { dosa: 80, idli: 40, vada: 35, filterCoffee: 30 };

const raised = Object.fromEntries(Object.entries(menu).map(([dish, price]) => [dish, Math.round(price * 1.05)]));
console.log('5 % more:', raised);

const under40 = Object.fromEntries(Object.entries(menu).filter(([, price]) => price < 40));
console.log('under 40:', under40);

const dishByPrice = Object.fromEntries(Object.entries(menu).map(([dish, price]) => [price, dish]));
console.log('swapped:', dishByPrice); // the prices became keys, and number-like keys come out in numeric order

// Any iterable of [key, value] pairs works, a Map for example. A key that comes twice keeps its last value.
console.log(Object.fromEntries(new Map([['tea', 20], ['lassi', 50]])));
console.log(Object.fromEntries([['size', 'small'], ['dish', 'dosa'], ['size', 'large']]));

console.log('original:', menu); // nothing above changed it

Output

5 % more: { dosa: 84, idli: 42, vada: 37, filterCoffee: 32 }
under 40: { vada: 35, filterCoffee: 30 }
swapped: { '30': 'filterCoffee', '35': 'vada', '40': 'idli', '80': 'dosa' }
{ tea: 20, lassi: 50 }
{ size: 'large', dish: 'dosa' }
original: { dosa: 80, idli: 40, vada: 35, filterCoffee: 30 }

Recorded with Node.js 24.21.0 on macOS 26 arm64. To run it yourself: mise exec node@24.21.0 -- node price-update.mjs

  • Change every value. map(([dish, price]) => [dish, Math.round(price * 1.05)]) raises each price by 5 % and rounds it, 35 becoming 37.
  • Keep some entries. filter(([, price]) => price < 40) keeps two dishes; the empty slot before the comma skips the key in the destructuring pattern.
  • Swap keys and values. The prices become keys, and because they are integer-like they come out in numeric order: 30, 35, 40, 80. This is the ordering rule at work, covered in the next section.
  • From other sources. A Map converts directly. When a key appears twice, the later value wins and the key keeps the place where it first appeared, as size shows. URLSearchParams is an iterable of pairs too, so Object.fromEntries(new URLSearchParams(location.search)) turns a page’s query string into an object.

The original menu is unchanged at the end: every step built a new array or a new object.

The order of keys

The specification fixes the order in which an object lists its own keys, in its OrdinaryOwnPropertyKeys steps:

  1. keys that are array indexes, in ascending numeric order;
  2. all other string keys, in the order they were created;
  3. symbol keys, in the order they were created.
The ordering rule on mixed keys, edge cases included JavaScript · key-order.mjs
// The order of an object's own keys: integer-like keys in ascending order, then other strings in the order they were
// added, then symbols in the order they were added.
const meta = Symbol('meta');
const mixed = { b: 1, 2: 1, a: 1, 1: 1, [meta]: 1, '-1': 1, '01': 1, 1.5: 1 };
const list = (keys) => keys.map(String).join(', ');
console.log('Object.keys:    ', list(Object.keys(mixed)));
console.log('Reflect.ownKeys:', list(Reflect.ownKeys(mixed)));
console.log('JSON:           ', JSON.stringify(mixed)); // JSON.stringify writes the same order

// Integer-like means an array index: a whole number from 0 to 4294967294, written without a sign, leading zeros or a
// decimal point.
const edges = { 4294967295: 'too big for an index', 4294967294: 'the largest index', 10: 'ten', 9: 'nine' };
console.log(list(Object.keys(edges)));

// Updating a key keeps its place; deleting it and adding it again moves it to the end.
const steps = { first: 1, second: 2, third: 3 };
steps.first = 10;
console.log(list(Object.keys(steps)));
delete steps.first;
steps.first = 1;
console.log(list(Object.keys(steps)));

Output

Object.keys:     1, 2, b, a, -1, 01, 1.5
Reflect.ownKeys: 1, 2, b, a, -1, 01, 1.5, Symbol(meta)
JSON:            {"1":1,"2":1,"b":1,"a":1,"-1":1,"01":1,"1.5":1}
9, 10, 4294967294, 4294967295
first, second, third
second, third, first

Recorded with Node.js 24.21.0 on macOS 26 arm64. To run it yourself: mise exec node@24.21.0 -- node key-order.mjs

An object's keys come in three groups: integer-like keys ascending, then other strings as added, then symbols as added.{ b, 2, a, 1, [meta], '-1', '01', 1.5 }keys in the order written1. integer-like keys,smallest first:'1', '2'2. other string keys,in the order added:'b', 'a', '-1', '01', '1.5'3. symbol keys,in the order added:Symbol(meta)Object.keys, entries, values,JSON.stringify and for...inshow groups 1 and 2;Reflect.ownKeys shows all threesorted intothenthen

The three groups that decide the order of an object's own keys

Text description of the diagram

The diagram starts from the example object, written with the keys `b`, `2`, `a`, `1`, a symbol called `meta`, `'-1'`, `'01'` and `1.5`, in that order, and shows the order JavaScript lists them in.

  1. First come the integer-like keys, the array indexes, smallest first: `'1'`, then `'2'`.
  2. Then come all other string keys in the order they were added: `'b'`, `'a'`, `'-1'`, `'01'` and `'1.5'`. Negative numbers, leading zeros and decimals are not integer-like, so they stay in this group.
  3. Last come symbol keys in the order they were added: `Symbol(meta)`.

`Object.keys`, `Object.entries`, `Object.values`, `JSON.stringify` and `for...in` list the own keys in groups 1 and 2 only; `Reflect.ownKeys` lists all three groups.

An array index is a key that reads exactly like a whole number from 0 to 4,294,967,294 (2 to the power 32, minus 2). Being number-like is not enough:

  • '1' and '2' are indexes, so they jump ahead of 'b' and 'a' although they were written later.
  • '-1' has a sign, '01' a leading zero and '1.5' a decimal point, so all three are ordinary strings and stay in the order they were written.
  • 4294967295 is one past the largest index, so it is an ordinary string key too and comes after the indexes 9, 10 and 4294967294.

Updating an existing key keeps its place, but deleting a key and adding it again creates a new property, which goes to the end of the string keys. Object.keys, Object.values, Object.entries and JSON.stringify all follow this order, and so does for...in for an object’s own keys in ordinary cases. Reflect.ownKeys adds the symbols at the end.

Numeric ids: when the order bites

The rule is harmless for keys such as names, and it causes a real bug when the keys are numbers that carry meaning, such as order ids sent newest first:

Orders keyed by id lose the order they were sent in JavaScript · ids-order.mjs
// An API sends three orders keyed by id, newest first. As an object, the ids come back in numeric order instead.
const byId = JSON.parse('{"1042": "Asha", "987": "Ravi", "1001": "Meera"}');
console.log(Object.keys(byId));
console.log(Object.values(byId)); // the newest-first order is gone

// Keys that are not integer-like keep the order they arrived in.
const prefixed = JSON.parse('{"o1042": "Asha", "o987": "Ravi", "o1001": "Meera"}');
console.log(Object.keys(prefixed));

// When order matters, send an array; to look orders up by id afterwards, build a Map, which keeps insertion order.
const orders = JSON.parse('[{"id": 1042, "name": "Asha"}, {"id": 987, "name": "Ravi"}, {"id": 1001, "name": "Meera"}]');
console.log(orders.map((order) => order.id));
const nameById = new Map(orders.map((order) => [order.id, order.name]));
console.log([...nameById.keys()], nameById.get(987));

Output

[ '987', '1001', '1042' ]
[ 'Ravi', 'Meera', 'Asha' ]
[ 'o1042', 'o987', 'o1001' ]
[ 1042, 987, 1001 ]
[ 1042, 987, 1001 ] Ravi

Recorded with Node.js 24.21.0 on macOS 26 arm64. To run it yourself: mise exec node@24.21.0 -- node ids-order.mjs

The server sent 1042, 987 and 1001, newest first. Parsed into an object, they come back as 987, 1001, 1042, so a page that lists Object.values(byId) shows the oldest order first, and no code anywhere sorted anything. The fixes, from best to worst:

  • Send an array when order matters. Arrays keep their order by definition, and each order can carry its id.
  • Build a Map when you need lookups by id as well. A Map keeps insertion order for every kind of key, numbers included, as nameById.keys() shows.
  • Prefix the keys, such as "o1042", so they are not integer-like. It works, but every reader must know the trick.
JSON Formatter Format a JSON response to see whether ids arrive as object keys or as an array, before you rely on their order.

The other key lists

Object.keys is one of five ways to list keys, and they differ in which keys they include:

Five key lists on one object JavaScript · all-keys.mjs
// Five ways to list an object's keys, on one object with an inherited, a hidden and a symbol key.
const base = { inherited: true };
const item = Object.create(base);
item.name = 'pen';
item[Symbol('sku')] = 'P-1';
Object.defineProperty(item, 'internal', { value: 42, enumerable: false }); // a non-enumerable property

console.log('Object.keys               ', Object.keys(item));
console.log('Object.getOwnPropertyNames', Object.getOwnPropertyNames(item));
console.log('Object.getOwnPropertySymbols', Object.getOwnPropertySymbols(item));
console.log('Reflect.ownKeys           ', Reflect.ownKeys(item));
const inLoop = [];
for (const key in item) inLoop.push(key);
console.log('for...in                  ', inLoop);

Output

Object.keys                [ 'name' ]
Object.getOwnPropertyNames [ 'name', 'internal' ]
Object.getOwnPropertySymbols [ Symbol(sku) ]
Reflect.ownKeys            [ 'name', 'internal', Symbol(sku) ]
for...in                   [ 'name', 'inherited' ]

Recorded with Node.js 24.21.0 on macOS 26 arm64. To run it yourself: mise exec node@24.21.0 -- node all-keys.mjs

Which keys each way of listing includes
Criterion Object.keys / values / entriesObject.getOwnPropertyNamesObject.getOwnPropertySymbolsReflect.ownKeysfor...in
Own string keys Enumerable onesAllNoneAllEnumerable ones
Non-enumerable keys NoYesYes, for symbolsYesNo
Symbol keys NoNoYesYesNo
Inherited keys NoNoNoNoEnumerable ones
When to choose Working with an object's dataInspecting hidden propertiesReading symbol keysEvery own key, for copying or debuggingRarely: it walks the prototype chain

Exercise: mapValues and pickBy

Exercise · Easy · JavaScript

Write mapValues and pickBy for plain objects

Write two helpers in object-tools.mjs that work on an object's own enumerable string keys, the keys Object.entries lists:

  • mapValues(obj, fn) returns a new object with the same keys, where each value is fn(value, key): mapValues({ dosa: 80, idli: 40 }, (price) => price * 2) gives { dosa: 160, idli: 80 }.
  • pickBy(obj, predicate) returns a new object with only the entries for which predicate(value, key) is truthy. With no predicate, it keeps every entry whose value is not undefined, which is handy for dropping missing fields before sending JSON.

Both leave obj unchanged, ignore inherited and symbol keys, keep the usual key order, and treat every key the same, __proto__ included. The versions in the file use for...in and change their input; the tests show what goes wrong.

Starter code · object-tools.mjs

// mapValues and pickBy for plain objects (the exercise of lesson javascript/object-iteration-and-key-order).
// These versions use for...in and change their input. Make them follow the rules of the prompt.

export function mapValues(obj, fn) {
  const result = {};
  for (const key in obj) result[key] = fn(obj[key], key);
  return result;
}

export function pickBy(obj, predicate) {
  for (const key in obj) {
    if (!predicate(obj[key], key)) delete obj[key];
  }
  return obj;
}
The sample tests · object-tools.test.mjs
import { test, assert } from 'mysmartcopilot:test';
import { mapValues, pickBy } from './object-tools.mjs';

test('mapValues applies the function to every value and passes the key', () => {
  assert.deepEqual(mapValues({ dosa: 80, idli: 40 }, (price) => price * 2), { dosa: 160, idli: 80 });
  assert.deepEqual(mapValues({ size: 'large' }, (value, key) => `${key}=${value}`), { size: 'size=large' });
});

test('pickBy keeps the entries the predicate accepts', () => {
  const menu = { dosa: 80, idli: 40, vada: 35 };
  assert.deepEqual(pickBy(menu, (price) => price < 50), { idli: 40, vada: 35 });
  assert.deepEqual(pickBy(menu, (price, dish) => dish.startsWith('d')), { dosa: 80 });
});

test('pickBy without a predicate drops only undefined values', () => {
  const form = { name: 'Asha', phone: undefined, coupon: null, notes: '' };
  assert.deepEqual(Object.entries(pickBy(form)), [
    ['name', 'Asha'],
    ['coupon', null],
    ['notes', ''],
  ]);
});

test('neither helper changes its input, and both return a new object', () => {
  const menu = { dosa: 80, idli: 40 };
  const mapped = mapValues(menu, (price) => price + 1);
  const picked = pickBy(menu, () => false);
  assert.deepEqual(menu, { dosa: 80, idli: 40 });
  assert.notEqual(mapped, menu);
  assert.notEqual(picked, menu);
});

test('inherited and symbol keys are ignored', () => {
  const special = Object.create({ inherited: 1 });
  special.own = 2;
  special[Symbol('note')] = 3;
  assert.deepEqual(Reflect.ownKeys(mapValues(special, (value) => value * 10)), ['own']);
  assert.deepEqual(Reflect.ownKeys(pickBy(special, () => true)), ['own']);
});

test('number-like keys keep their order and __proto__ is an ordinary key', () => {
  const byId = { 10: 'b', 2: 'a', name: 'c' };
  assert.deepEqual(Object.entries(mapValues(byId, (s) => s.toUpperCase())), [
    ['2', 'A'],
    ['10', 'B'],
    ['name', 'C'],
  ]);
  const odd = JSON.parse('{"__proto__": 1, "tea": 2}');
  assert.deepEqual(Object.entries(mapValues(odd, (n) => n * 10)), [
    ['__proto__', 10],
    ['tea', 20],
  ]);
});
A hint

Object.entries(obj) gives exactly the keys you need, own, enumerable and strings, as [key, value] pairs. Change the pairs with map or filter, then rebuild an object with Object.fromEntries. fromEntries defines each key as an ordinary property, so even '__proto__' becomes a key, which plain assignment such as result[key] = value would not do.

The sample tests run on this device, in your browser (QuickJS): nothing is sent to mysmartcopilot.com. The first run downloads JavaScript (about 0.6 MB), which is kept for the next runs. A check in your browser is feedback for you, not proof that the code is right for every input.

Self-check

Check yourself

6 questions about this lesson. Every answer and why it is right is on the page, behind “Show the answer”. Your score stays in this browser.

  1. Question 1 of 6 What does this program print?

    Read the code, then choose one answer.

    console.log(Object.keys({ z: 1, 10: 1, a: 1, 2: 1 }));
    Show the answer to question 1

    Answer: [ '2', '10', 'z', 'a' ]

    Integer-like keys come first, in numeric order (2 before 10, not "10" before "2" as text), then the other string keys in the order they were written, z before a.

  2. Question 2 of 6 What does this program print?

    Read the code, then choose one answer.

    const settings = Object.fromEntries([['b', 1], ['a', 2], ['b', 3]]);
    
    console.log(JSON.stringify(settings));
    Show the answer to question 2

    Answer: {"b":3,"a":2}

    Object.fromEntries adds the pairs one by one. The second b updates the value of the existing key, and updating a key does not move it, so b stays first with its last value.

  3. Question 3 of 6 const child = Object.create({ shared: 1 }); child.own = 2; What do Object.keys(child) and a for...in loop list?

    Choose one answer.

    Show the answer to question 3

    Answer: Object.keys gives [ 'own' ]; for...in gives own, then shared

    Object.keys, values and entries list only the object's own enumerable string keys. for...in also walks enumerable keys the object inherits, after its own ones.

  4. Question 4 of 6 What does this program print?

    Read the code, then choose one answer.

    const id = Symbol('id');
    const user = { [id]: 7, name: 'Asha' };
    
    console.log(Object.keys(user).length, Reflect.ownKeys(user).length, JSON.stringify(user));
    Show the answer to question 4

    Answer: 1 2 {"name":"Asha"}

    Symbol keys are left out of Object.keys, Object.entries and JSON.stringify. Reflect.ownKeys lists every own key, symbols and non-enumerable keys included.

  5. Question 5 of 6 What does this program print?

    Read the code, then choose one answer.

    const order = { c: 1, a: 2 };
    delete order.c;
    order.c = 3;
    
    console.log(Object.keys(order));
    Show the answer to question 5

    Answer: [ 'a', 'c' ]

    Deleting a key and adding it again creates a new property, which goes to the end of the string keys. Assigning a new value without deleting would have kept c in first place.

  6. Question 6 of 6 What does this program print?

    Read the code, then choose one answer.

    console.log(Object.keys({ '007': 'a', 7: 'b', '-7': 'c' }));
    Show the answer to question 6

    Answer: [ '7', '007', '-7' ]

    Only '7' is an array index. '007' has leading zeros and '-7' a sign, so both are ordinary strings and keep the order they were written in, after the integer-like key.

Interview questions

Fresher · In what order does Object.keys return an object’s keys?

The order is fixed by the specification, not by chance. First come the keys that look like array indexes, whole numbers from 0 up to 4,294,967,294 written without a sign, leading zeros or a decimal point, in ascending numeric order. Then come all other string keys in the order they were added to the object. So { b: 1, 2: 1, a: 1, 1: 1 } gives ['1', '2', 'b', 'a']. Symbol keys come last, in the order they were added, but Object.keys leaves them out; Reflect.ownKeys includes them. Updating a key keeps its place, while deleting it and adding it again moves it to the end. Object.values, Object.entries and JSON.stringify use the same order, which is why numeric ids in an object come back sorted: use an array or a Map when insertion order matters.

Model answers open with any pass: ₹79 for 1 week ($2.99), one payment that never renews.

  1. Mid to senior

    Two services sign the same order by hashing JSON.stringify(order) with SHA-256. After one service is refactored, the signatures stop matching although both orders hold equal data. A teammate proposes sorting the keys into a new object first. Explain the mismatch, why the proposal still fails for some keys, and what to do.

    Model answer, Pro. Model answers open with any pass; prices are in the notice on this page. First step and follow-ups

  2. Fresher to mid

    To rank players, a function inverts { asha: 980, ravi: 1200, meera: 1100 } into an object keyed by score, then reads Object.values() of it as the ranking. The ranking comes out lowest score first, and when two players tie, one of them disappears. Explain both bugs and write a correct ranking.

    Model answer, Pro. First step and follow-ups

Key takeaways

  • Object.keys, Object.values and Object.entries turn an object’s own, enumerable, string keys into arrays; inherited and symbol keys are left out.
  • Object.fromEntries(pairs) builds an object from any iterable of [key, value] pairs: entries, a Map or URLSearchParams. Together with map and filter it transforms objects without changing the original.
  • Own keys come out in a fixed order: array-index keys in numeric order, then other strings in creation order, then symbols. '01', '-1' and '1.5' are not indexes.
  • Updating a key keeps its place; deleting and re-adding it moves it to the end.
  • Objects keyed by numeric ids lose their insertion order. Send arrays, or use a Map, when order matters.

References

Related tools

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.