Moonwalking JavaScript Objects: How to Reverse Iterate Object in JavaScript


In this blogpost, we will see ways to loop through Object keys in reverse order in JavaScript. We’ll also explore Generators, ES6 Object property ordering nuances, and how an Array and Object differ in their ordering, avoiding mutation while iteration, and a common error with for…in loop while iterating Objects.

Lets dive in, and make you JavaScript Object Moon-Walking expert 🕺!


Refresher: Objects in JavaScript:

  • JavaScript objects are collections of properties, where each property is a key-value pair. Usually, objects are iterated in the order of key insertion, but sometimes, you might need to traverse them backward.
Basic looping on JavaScript Object (JavaScript)
const obj = { a: 'one', b: 'two', c: 'three' };
for (const key in obj) {
  console.log(`${key}: ${obj[key]}`);
}
// Output:
//  a: one
//  b: two
//  c: three

Ways to reverse iterate on JavaScript Set

Using Object.keys() with reverse():

  • Extract keys using Object.keys().
  • Reverse the array with reverse(). Note that reverse() mutates the array it acts upon. So here the array generated by Object.keys() is mutated by reverse(), but the object itself is untouched.
  • Iterate using forEach().
Object.keys() & reverse() (JavaScript)
const obj = { a: 'one', b: 'two', c: 'three' };

const reversedKeys = Object.keys(obj).reverse();

reversedKeys.forEach(key => {
  console.log(`${key}: ${obj[key]}`);
});

// Output:
//  c: three
//  b: two
//  a: one

Using spread operator () with Object.keys() and reverse()

  • Extract keys using Object.keys().
  • Reverse the array with reverse(). Note that reverse() mutates the array it acts upon. So here the array generated by Object.keys() is mutated by reverse(), but the object itself is untouched.
  • Iterate using forEach().
Spread, Object.keys() & reverse() (JavaScript)
const obj = { a: 'one', b: 'two', c: 'three' };

const keys = [...Object.keys(obj)];

const reversed = keys.reverse();
reversed.forEach(key => {
  console.log(`${key}: ${obj[key]}`);
});

// Output:
//  c: three
//  b: two
//  a: one

Using Object.values() with reverse():

  • Extract keys using Object.values().
  • Reverse the array with reverse(). Note that reverse() mutates the array it acts upon. So here the array generated by Object.values() is mutated by reverse(), but the object itself is untouched.
  • Iterate using forEach().
Object.values() & reverse() (JavaScript)
const obj = { a: 'one', b: 'two', c: 'three' };

const reversedValues = Object.values(obj).reverse();

reversedValues.forEach(value => {
  console.log(value);
});

// Output:
//  three
//  two
//  one

Using Object.entries() with reverse():

  • Extract keys using Object.entries().
  • Reverse the array with reverse(). Note that reverse() mutates the array it acts upon. So here the array generated by Object.entries() is mutated by reverse(), but the object itself is untouched.
  • Iterate using forEach().
Object.entries() & reverse() (JavaScript)
const obj = { a: 'one', b: 'two', c: 'three' };

const reversedEntries = Object.entries(obj).reverse();

reversedEntries.forEach(([key, value]) => {
  console.log(`${key}: ${value}`);
});

// Output:
//  c: three
//  b: two
//  a: one

Using Generators

Generators provide a powerful way to create intricate iteration behavior, which can be useful while reverse iterating object keys. While simple loops can use break and continue, generators offer additional flexibility: like pausing or conditional continuation.

  • State Preservation: Generators maintain their state, allowing for resumption of iteration at specific points.
  • Custom Iteration Logic: Generators can yield values based on complex conditions, beyond simple breaks and continues.
Generator to reverse Iterate Object (JavaScript)
function* reverseObjectIteratorWithPause(obj) {
  const keys = Object.keys(obj).reverse(); // Reverse the keys
  for (let key of keys) {
    if (obj[key] === 'pause') {
      yield 'Iteration paused';
    } else {
      yield `Key: ${key}, Value: ${obj[key]}`;
    }
  }
}

// Example object
const obj = { a: 1, b: 'pause', c: 3, d: 4 };

// Using the generator
const iterator = reverseObjectIteratorWithPause(obj);
console.log(iterator.next().value); // Key: d, Value: 4
console.log(iterator.next().value); // Key: c, Value: 3
console.log(iterator.next().value); // Iteration paused
console.log(iterator.next().value); // Key: b, Value: pause
  • The generator reverseObjectIteratorWithPause iterates over the object’s keys in reverse order.
  • When it encounters a value equal to 'pause', it yields a message indicating that the iteration is paused. This showcases the ability of generators to handle conditional logic within iterations.
  • This level of control, especially with the ability to yield specific messages or pause iteration based on conditions, is not as straightforward with conventional loops.

Tips on reverse iterating Object keys in Javascript

ECMAScript 2015 (ES6): Object Property Enumeration Order

ES6 specifies property order in objects: integer keys in ascending order, followed by string and symbol keys in creation order. So they may not always be listed in the order in which you added them, and even your iteration order may not be as expected. Further, relying completely on even ES6 property order can be risky, especially when considering different JavaScript engines or older versions.

For now, lets illustrate this as per ES6 specs. Let’s consider an object with multiple numeric, string, and symbol keys.

ES6 Object Property Ordering (JavaScript)
const sym1 = Symbol('sym1');
const sym2 = Symbol('sym2');

const mixedObj = {
  '30': 'numericKey1',
  '10': 'numericKey2',
  'b': 'stringKey1',
  'a': 'stringKey2',
  [sym1]: 'symbolKey1',
  [sym2]: 'symbolKey2'
};

// Logs properties in the order: numeric keys in ascending order, string keys, symbol keys
console.log(Reflect.ownKeys(mixedObj)); 
// Output: ['10', '30', 'b', 'a', Symbol(sym1), Symbol(sym2)]
  • Numeric keys (’10’, ’30’) are sorted numerically, followed by string keys (‘b’, ‘a’) in order of creation, and finally symbol keys (sym1, sym2) in order of creation as well.

Iteration order differences in Array & Object

Arrays have a predictable iteration order, based on index positions. Object properties have a specific order as per ES6, but it’s different from arrays.

Ordering difference Array vs Object (JavaScript)
const arr = ['first', 'second', 'third'];
const obj = { '0': 'first', '2': 'third', '1': 'second' };

// Reverse Array Iteration
for (let i = arr.length - 1; i >= 0; i--) {
  console.log('Array Order: ',arr[i]);
}

// Reverse Object Iteration
const keys = Object.keys(obj);
console.log("Keys: ", keys);
for (let i = keys.length - 1; i >= 0; i--) {
  console.log('Object Order: ',obj[keys[i]]);
}

// Output: 👇
//   Array Order:  third
//   Array Order:  second
//   Array Order:  first
//
//   Keys:  [ '0', '1', '2' ]
//   Object Order:  third
//   Object Order:  second
//   Object Order:  first
  • The order of elements in arr is index-based, whereas obj follows ES6 property order rules.
  • The reverse iteration of arr and obj demonstrates how array elements follow the index order, while object properties adhere to the specified ES6 order, which may differ from the array index order.

Inherited properties included in for…in loop

The for...in loop in JavaScript iterates over the enumerable properties of an object, in the order defined as per ES6 specifications. However, it’s important to note that this loop iterates over all enumerable properties, including those inherited through the prototype chain.

But, by adding the Object.hasOwnProperty check, you can ensure that only the object’s own properties are considered.

Inherited properties & for…in loop (JavaScript)
// Define a prototype with properties
const prototypeObj = {
  inheritedProperty: 'inherited value'
};

// Create a new object that inherits from prototypeObj
const obj = Object.create(prototypeObj);
obj.ownProperty1 = 'value1';
obj.ownProperty2 = 'value2';

// Using for...in loop without hasOwnProperty check
for (let key in obj) {
  console.log(`${key}: ${obj[key]}`);
}
// Output includes:
// ownProperty1: value1
// ownProperty2: value2
// inheritedProperty: inherited value

// - - Revised loop with hasOwnProperty - - 

// Using for...in loop with hasOwnProperty check
for (let key in obj) {
  if (obj.hasOwnProperty(key)) {
    console.log(`${key}: ${obj[key]}`);
  }
}
// Output includes only the object's own properties:
// ownProperty1: value1
// ownProperty2: value2
  • First, we create a prototype object and a new object that inherits from this prototype.
  • We can see inheritedProperty is logged alongside the object’s own properties, demonstrating how the for...in loop includes inherited properties.
  • In this revised loop, inheritedProperty is not logged, as obj.hasOwnProperty(key) filters out properties that are not directly on obj but are inherited from its prototype.

So how do you reverse Object using for…in loop? You cant directly access index, so you’ll need to store the keys in an array, reverse this array, and then iterate over it.

for…in loop to reverse iterate Object (JavaScript)
const obj = { a: 'one', b: 'two', c: 'three' };
let keys = [];

for (let key in obj) {
  if (obj.hasOwnProperty(key)) {
    keys.push(key);
  }
}

keys.reverse(); // ['c', 'b', 'a']
keys.forEach(key => console.log(`${key}: ${obj[key]}`));
// Output:
// c: three
// b: two
// a: one

Avoid mutation while iteration

When iterating over an object’s properties, adding or removing elements during the iteration can lead to confusing behaviors.

In following example, at a particular iteration, we will remove an element already iterated, being iterated and going to be iterated, and then also add an element. Lets see what this mutation while iteration gives.

Avoid Object mutation while iteration (JavaScript)
const obj = { a: 1, b: 2, c: 3 };

Object.keys(obj).forEach((key, index) => {
  console.log(`Before mutation: ${key}, ${obj[key]}`); 

  if (key === 'b') {
    delete obj['a']; // Deleting already iterated property
    delete obj['b']; // Deleting an existing property
    delete obj['c']; // Deleting upcoming iteration property
    obj['d'] = 4; // Adding a new property
  }

  console.log(`After mutation: ${key}, ${obj[key]}`);
});

console.log('Final object:', obj);

// Output: 👇
//  Before mutation: a, 1
//  After mutation: a, 1
//  Before mutation: b, 2
//  After mutation: b, undefined
//  Before mutation: c, undefined
//  After mutation: c, undefined
//  Final object: { d: 4 }
  • Note that Object.keys(obj) has already produced an array with value [a,b,c], and we are iterating with forEach on this array of keys, but inside those iterations we are mutating elements from the Object itself. Also, once the array [a,b,c] is produced by Object.keys(), it is unlinked to obj and will not reflect mutation of keys in obj.
  • The first iteration (with key a) logs the current state of the object before any mutation.
  • In the second iteration (with key b), the object is mutated: current iteration key (b), previous iteration key (a) and next iteration key (c) are deleted, and 'd': 4 is added.
  • The console logs during iteration show how a is unaffected (since already iterated, b got deleted after mutation, and that c is deleted for the next iteration. And that d is never iterated, since it wasn’t part of the array produced by Object.keys(obj).
  • The final state of the object, after the loop completes, reflects the deletions of 'a','b','c' and the addition of 'd'. Only 'd' remains in the end.

Performance tips for Large Objects:

Object.keys() and for loop is faster than Object.keys().reverse().forEach() (where there is an additional reverse() call).

Performance: avoiding extra reverse() call (JavaScript)
const largeObj = {};
for (let i = 0; i < 1000_000; i++) {
  largeObj[`key${i}`] = i;
}

// Technique 1: Using Object.keys() with reverse()
console.time('With reverse');
Object.keys(largeObj).reverse().forEach(key => {});
console.timeEnd('With reverse');

// Technique 2: Without using reverse()
console.time('Without reverse');
const keys = Object.keys(largeObj);
for (let i = keys.length - 1; i >= 0; i--) {
  // Iteration logic here
}
console.timeEnd('Without reverse');

// Timing on my machine (for 1000_000 iterations)
//   With reverse: 419.276ms
//   Without reverse: 389.834ms
  • Technique 1 involves the extra step of reversing the array of keys, which could impact performance, especially for large objects.
  • Technique 2 avoids the reverse() method, potentially offering better performance for iterating over large objects.

🧪Practice Coding Problem: Backward Summation Safari 🌳🐎

In the spirit of Test Driven Development ( 😁), lets test our understanding by solving a problem.

Write a function which does a “Backward Summation Safari”, where it’ll traverse the properties of a JavaScript object in reverse order, adding up only numerical values it finds the trail.

Problem (JavaScript)
/**
 * Goes on a safari through an object's properties in reverse, summing all numerical values.
 * @param {Object} obj - The object to explore.
 * @return {number} - The total sum of numerical property values encountered.
 */
function backwardSummationSafari(obj) {
  // > > > 👉 Write code here 👈 < < <
}


// Driver code
const safariObject = { zebra: 3, lion: 7, elephant: 2, garbage: 'value' };
console.log(backwardSummationSafari(safariObject));
// Expected Output: The sum of the values (3 + 7 + 2 = 12) when iterated in reverse order
// skipped pair (garbage: 'value').
Please attempt before seeing the Answer:
Solution (JavaScript)
function backwardSummationSafari(obj) {
  const keys = Object.keys(obj).reverse();
  let totalSum = 0;

  keys.forEach(key => {
    if (typeof obj[key] === 'number') {
      totalSum += obj[key];
    }
  });

  return totalSum;
}

Explanation:

  • The backwardSummationSafari function grabs all the property keys of the object and reverses their order.
  • Then initialises totalSum as 0, & iterates over the reversed keys.
  • And accumulates only numerical values in totalSum.

So you are an expert in Moonwalking Objects now. 🤓 Great! 🎉

But don’t stop, and keep coding! 🚀👨‍💻

Scroll to Top