The JavaScript array. A simple, elegant data structure. Easy to make them. Easy to add data to them. Easy to sort them. Easy to iterate on them. What could possibly be weird about JavaScript arrays?

First, a primer on making arrays
There’s a few different ways to make arrays in JavaScript. They amount to, “you can instantiate them with data, or without data”.
Instantiating with data
Sometimes we need to write an array with data already in it. It’s not uncommon to do this when you don’t expect that list to really change after you declare it. So there’s two ways we instantiate that array:
Using the Array constructor
const mistakes = new Array('my liberal arts degree', 'eating 15 slim jims')`
This is considered weird and generally a bad practice (in JavaScript). I’ll explain why in a bit.
Using the Array Literal
const moreMistakes = [tequila', 'Michael Bay\'s TMNT movie'];
This is the more common approach that I hope most of us are using at home.
Instantiating without data
In cases where we’re moving data from one data structure to another we often declare an empty array and then modify it.
An extremely common pattern is declaring the empty array and then pushing to it:
const moreFutureMistakes = [];
moreFutureMistakes.push('TMNT sequels')
But if you want to be that person, of course you can use the array constructor:
const moreUnusualMistakes = new Array();
moreUnusualMistakes.push('what you\'re going to see next');
Weird ways to instantiate arrays
I don’t think I’ve ever really seen these in the wild, but they’ve always been there in the back of my mind. Kind of like the Hawaii 5-0 theme song. It’s not really doing anything in my brain other than sitting there. Making sure I don’t forget it.
One of the only things I remember from my C++ class was that arrays had to have sizes. And I didn’t know why. I still don’t know why. (answer:somethingSomething[memory])
So three weird ways of instantiating arrays involve setting the size of it up front:
const superSizeMe = []; superSizeMe.length = 3; // SURPRISE! Length is a setter const preSized = new Array(3); // "This won't confuse anyone," said no one ever. const commaSized= [,,,]; const isWeirdButTrue= (superSizeMe.length === preSized.length === commaSized.length);
If you were wondering why it’s considered bad practice to use the Array constructor, now you know:
It’s because if you give it exactly one argument, and that argument is an Integer, it will create an array of that size. So the array constructor could get unexpected results when dealing with numbers.
And it’s not like any of the other approaches are remotely close to best-practice, either. They’re all the strange practices of someone who’s maybe a little too curious for their own good or perhaps wondering if the trickster-god Loki is in fact alive and designing programming languages.
Weird instantiations and weird ways to set data result in weird results totally expected behavior.
Cool, now we’re caught up to the initial weird thing we saw in the screenshot:
[,,,3,4,,5].forEach(x=>console.log(x)); // 3 // 4 // 5
- Ok, let’s agree that comma-instantiated arrays is weird.
- Ok. It logged… 3,4,5
This is fine. Everything’s fine. Those other slots must be undefined or unavailable.
for (let x of [,,,3,4,,5]) { console.log(x); }
// undefined
// undefined
// undefined
// 3
// 4
// undefined
// 5
Hold up….
What’s in those “weirdly instantiated arrays” ?
Let’s take a step back and look at these pre-sized arrays:
const myRegrets = new Array(3); const moreRegrets = [,,,]; const noRegerts = []; noRegerts.length = 3;
If you’re using Firefox, crack open the console, run this, and take a look at those arrays.
You might see something like:
Array(3) [undefined, undefined, undefined]
But is that array really filled with three undefined?
No. Not it is not. That’s what’s so freaking weird about arrays.
If you loop through these “pre-sized” arrays, and try to log the value, you will not get a log:
const myRegrets = new Array(3);
myRegrets.forEach((regret, regretIndex) => {
console.log(regret, regretIndex);
});
for (regretName in myRegrets) {
console.log(regretName, myRegrets[regretName]);
}
So, the first big takeaway here is that a pre-sized array, or an array created with comma/slots, doesn’t have a value in those slots.
myRegrets is not an array with 3 undefined. It’s an array with three slots of nothing.
To further prove this point, add an actual undefined to the third slot:
const myRegrets = new Array(3);
myRegrets[1] = undefined;
myRegrets.forEach((regret, regretIndex) => {
console.log(regret, regretIndex);
});
for (regretName in myRegrets) {
console.log(regretName, myRegrets[regretName]);
}
You got a log, didn’t you? Just one, right?
Double-you Tee Eff
There’s an implicit and explicit undefined in Arrays
When we do these weird Array tricks where we pre-size it, or we comma-slot it (e.g. [,,undefined]), JavaScript isn’t actually putting values in those slots. Instead, it’s saying the slots exist … kinda.
If something exists, but has no value, we have a name for that:
undefined
const myRegrets = [,,undefined]; const youWillRegretThis; myRegrets[0] === youWillRegretThis; // true, so very true
But I call this “implicit undefined” because this doesn’t get logged in any looping that we do. Not forEach, neither for - in, nor map and its buddies will log a slot that has no value; i.e. implicitly undefined;
You could also call this “undeclared” if you don’t like “implicitly undefined.” You can call it Al, I really don’t care.
Explicit undefined must take up memory
When you loop over an array with an explicit undefined , it must be taking up actual memory. And that’s why it gets logged:
const myRegrets = [,,undefined];
myRegrets.forEach((regret, regretIndex) => {
console.log(regret, regretIndex);
});
// will log 3
for (regretName in myRegrets) {
console.log(regretName, myRegrets[regretName]);
}
// will log 3
So just remember this, kids:
There’s an implicit undefined and an explicit undefined when it comes to arrays.
This probably won’t be a thing you trip on any time soon unless you merge arrays.
Or use for of…
Wait. for of?
Yes
(Double-you tee ay eff)
for - of is the only looping mechanism that doesn’t care about implicit or explicit undefined.
Again, think of “implicit undefined” as meaning, “undeclared”:
const myRegrets = [,,undefined];
let regretCounter = 0;
for (regret of myRegrets) {
console.log(regret, regretCounter++)
}
// undefined 0
// undefined 1
// undefined 2
Why did it log all three?
I don’t know for certain, but I have a theory that a much smarter person than I needs to investigate.
The for of pattern implements an iteration protocol
My theory here is that the iteration protocol on arrays behaves something like what’s shown from the examples page:
function makeIterator(array) {
var nextIndex = 0;
return {
next: function() {
return nextIndex < array.length ?
{value: array[nextIndex++], done: false} :
{done: true};
}
};
}
if for - of implements something like this under the hood, this will iterate by index, not property (and arrays are really just objects with properties that are numbers. kinda).
So, back to what we call a thing that exists but has no value. You know the one. Our old friend. The one we never invite to parties but he shows up anyway? Remember him?
undefined
I’m really starting to not like that guy. He weirds me out.
TL;DR
- The
.lengthproperty of an array isn’t the same as saying, “these many slots have value” - Arrays can have “implicit undefined”, and “explicit undefined”, depending on whether the space really has a value and “implicit undefined” is more like, “undeclared”
- If you don’t create arrays in weird ways, or do weird things with arrays, you’ll likely never encounter this
- If you are that trickster god Loki, or a fan, and choose to create arrays in weird ways, or manipulate them weirdly, you may need to use
for ofto get the most consistent result while looping.
JavaScript makes a lot of sense early in the morning. Not late at night.
Bonus: Some array FAQs
Look I’m proud of you for making it this far. You don’t have to keep going. Turn off your laptop, pour yourself a bourbon, and watch Community or Parks & Rec. You earned it.
Unless you’re camped out in a bathroom stall on account of a late-night trist with a block of cheese, or have some weird interest in specifications, you can stop reading now.
Are there array properties that are simply being set as not enumerable?
I don’t think so.
I skimmed through some ECMA specs to see if this behavior is defined anywhere.
The specs say that array elements without assignment expressions are not defined
Section 12.2.5 says
Whenever a comma in the element list is not preceded by an AssignmentExpression (i.e., a comma at the beginning or after another comma), the missing array element contributes to the length of the Array and increases the index of subsequent elements. Elided array elements are not defined.
So if you have [,,'foo'], those array elements after the comma that don’t have some sort of expression are “elided”.
Also worth noting is that the specs say that ['foo',] does not contribute to the length of the array.
Also also worth noting is that I haven’t yet found if pushing values to a random index above the length counts as elision. e.g.:
const gapped = []; gapped[2] = "please mind the gap";
The specs don’t seem to state that array elements are created but not enumerable
Step 8 of section 22.1.1.3 describes how an array is created:
Repeat, while k < numberOfArgs
a. Let Pk be ! ToString(k)
b. Let itemK be items[k]
c. Let defineStatus be CreateDataProperty(array, Pk, itemK)
d. Assert: defineStatus is true
e. Increase k by 1
Pk is the key (which is an index) and itemK is the value.
If the JavaScript engine is following this algorithm, an item, regardless of its value, gets passed into the CreateDataProperty function/method/whatever.
The question is, “does the first slot in [,,'foo'] constitute being an item? 12.2.5 says no. (I think)
But is there a chance CreateDataProperty is creating a property making it non-enumerable?
If you read in section 7.3.4, it doesn’t give any logic or condition where the enumerable property in the descriptor . Steps 3 and 4 set the property to be enumerable:
- Let newDesc be the PropertyDescriptor { [[Value]]: V, [[Writable]]: true, [[Enumerable]]: true, [[Configurable]]:true }.
- Return ? O.[DefineOwnProperty]
I haven’t read through all the specification has to say about arrays. But this seems further suggest that these “empty slots” are really empty.
What about the in operator. Will it find these empty slots?
No it will not!
const slotted = [,,'one'];
let i = 0;
while (i < slotted.length) {
if (i++ in slotted) {
console.log(`${i - 1} is in the array`);
}
}
This will log exactly once, displaying 2 is in the array in your console.
If you have an explicit undefined, howeverconst explicit = [undefined, undefined, 'one'] , it will log three times.
STL;SDR (Still too long, still didn’t read)
First, let me caveat everything by telling you I’m way more qualified to talk to you about French Existentialism than JavaScript. The likelihood that all this is wrong is pretty high.
Based on my understanding of the specifications, “implicit undefined” is a somewhat valid-ish way to describe a “slot” in an array that has no value.
Except, of course, there’s not really even a slot. There’s not even the idea of a slot. A slot does not exist without a value. (#Existentialism)
As Kyle Simpson points out there is a difference between undefined and undeclared but JavaScript doesn’t always give you messaging that makes it clear which is which.
This “implicit undefined” is more of an existential problem where we only have so many ways to describe Being and Nothingness.
const existentialCrisis= [,undefined,'Waiting for Godot']`; console.log(typeof existentialCrisis[1]); // undefined console.log(typeof existentialCrisis[0]); // undefined
![Screen grab of two javascript arrays. They both are [,,2,,4].
in one case, I forEach this array and only 2 and 4 are logged.
In another I for-of the array and undefined is logged twice, 2 is logged, undefined is logged again, and 4 is logged.
It illustrates that under different circumstances, arrays do or don't have information.](https://blog.frankmtaylor.com/wp-content/uploads/2023/05/image.png)