DEV Community

Alex MacArthur
Alex MacArthur

Posted on • Originally published at macarthur.me on

Why Can’t You .forEach() Over Empty Array Items?

Harken back to a time when you needed to execute some code a specific number of times and had some reason to not use a for loop. You might’ve made a fresh array with a certain number of items, and then attempted to .forEach() over it:

const freshArray = new Array(5);

freshArray.forEach(item => {
    console.log(item);
});

Enter fullscreen mode Exit fullscreen mode

If you did take that approach, you also might’ve expected to see five individual logs for undefined. But instead, nothing happened, as if the array didn’t have any items to begin with.

That’s because it technically doesn’t. When a number is passed into the Array constructor, no items are actually defined — not even undefined ones. Instead, only thelengthproperty is set, effectively reserving that many seats that may or may not be filled later. You’ve created a sparsearray. You can verify that by logging the array itself:

const freshArray = new Array(5);

console.log(freshArray);

// [<5 empty items>]

Enter fullscreen mode Exit fullscreen mode

When a method on the Array prototype (.map(), .forEach(), etc.) encounters such an array, it’s designed to skip over any uninitialized or deleted items. The position of those items doesn’t matter. For example, you could designate “empty” items by inserting commas:

const freshArray = [1, 2, , , 5];

console.log("Length:", freshArray.length);

freshArray.forEach(item => {
  console.log(item);
});

// Length: 5
// 1
// 2
// 5

Enter fullscreen mode Exit fullscreen mode

And even by directly setting them via index:

const freshArray = [];

freshArray[3] = "fourth item!";

console.log("Length:", freshArray.length);

freshArray.forEach((item, index) => {
  console.log(item, index);
});

// Length: 4
// "fourth item!" 3

Enter fullscreen mode Exit fullscreen mode

If you’re dead-set on using methods like .forEach() or .map() with a sparse array, you can reach for something like .fill() to populate each empty item with something first (even undefined).

Iterators Are Different, Though

That said, even those empty items are accessible using the Symbol.iterator method defined on the Array prototype. It’s implicitly accessed any time you use a for loop.

const freshArray = new Array(5);

for (const item of freshArray) {
  console.log(item);
}

// undefined
// undefined
// undefined
// undefined
// undefined

Enter fullscreen mode Exit fullscreen mode

It’s also in play under the hood when the spread operator is used, which means you could spread your sparse array, and immediately be able to perform a .forEach() on it:

const freshArray = new Array(5);

// .forEach() will work now!
[...freshArray].forEach((item, index) => {
  console.log(item, index);
});

// undefined 0
// undefined 1
// undefined 2
// undefined 3
// undefined 4

Enter fullscreen mode Exit fullscreen mode

If you wanted, you could use this iteration behavior to make your own variation of .forEach that’ll handle empty items (tack on methods to prototypes at your own risk!):

Array.prototype.inclusiveForEach = function(callback) {
  return [...this].forEach(callback);
}

const freshArray = new Array(5);

freshArray.inclusiveForEach((item, index) => {
  console.log(item, index);
});

// undefined 0
// undefined 1
// undefined 2
// undefined 3
// undefined 4

Enter fullscreen mode Exit fullscreen mode

All of these decisions are up to you, and highly dependent on your circumstances. At the very least, I hope this helps shed some light on why the code behaves the way it does, and offers some considerations to help you move forward.

Top comments (2)

Collapse
 
thecodingcrow profile image
thecodingcrow

nice to read, learned something new!

Collapse
 
alexmacarthur profile image
Alex MacArthur

glad to hear!