Skip to content
By Adam Davies

The Quirky Array Constructor and a Use for Holey Arrays in ES6

In JavaScript, an Array isn't like your typical array: it's an object with keys that are indexes. What this means is that you need to be aware that the length can be more than the number of elements and that there can be "holes".

The Array Constructor

In day-to-day development you'll probably just build up arrays using the literal syntax, like so:

var myArray = ['An element', 'Another']

However, there is a constructor for Array that initializes the length:

// Initialize an array with a length of 2, but without any elements.
var twoHoles = new Array(2)
twoHoles.length
// <- 2

You might think this creates an array with two empty spots for elements, but you'd be wrong! Instead, it literally just creates a blank array with the length property set to 2; it doesn't even assign undefined into the array at each index.

// No elements logged.
console.log(twoHoles)
// <- []
// Looping over elements here prints nothing.
twoHoles.forEach((element, index) =>
  console.log('Element ' + index + ' is ', element)
)
// <- undefined

However, most operations treat these holes as undefined:

// inspecting: it knows there's 2 holes...
inspect(twoHoles)
// <- [undefined × 2]
// Join those elements that aren’t even there.
twoHoles.join(,)
// <- “,”

This is true also for operations that change the array:

var aChangedArray = Array(2) // Two holes.
aChangedArray.push('third')  // Append a "third".
aChangedArray[0] = 'first'   // Fill the first hole.

console.log(aChangedArray)
// <- ["first", 2: "third"]

inspect(aChangedArray)
// <- [“first", undefined × 1, "third"]

However, map will skip holes, in effect preserving them:

aChangedArray.map(() => "*")
// <- ["*", undefined x 1, "*"]

As you can see, it's actually pretty inconsistent.

The Quirkiness

The Array constructor behaves quite differently depending on the number of arguments and their types. If you pass more than one argument then it will interpret the arguments as elements for the new array:

var twoArguments = Array('A', 'B')
// <- ['A', 'B']

Actually, it still does even if you pass in one argument:

var oneArgument = Array('A')
// <- ['A']

The only way to make the constructor consider the argument as the length is by giving it an argument which is a number:

var eightHolesNotAnArrayWithOneNumber = Array(8)
// <- [ undefined x 8 ]

Which doesn't work too well for floats:

var someNumbersFail = Array(2.3)
// <- Uncaught RangeError: Invalid array length(…)

Other Hole Punchers

Holes can creep in a few other interesting ways:

  1. Deleting an index.
var myArray = ['one', 'two', 'three']
delete myArray[0]
inspect(myArray)
// [undefined × 1, "two", "three"]
  1. Using two commas in a row within an array literal.
var myArray = ['one',,'three']
inspect(myArray)
// ["one", undefined × 1, "three"]
  1. Increasing the length property.
var myArray = ['one', 'two', 'three']
myArray.length = 10
inspect(myArray)
// ["one", "two", "three", undefined × 7]

A Use for Holey Arrays?

So should we put this down to a weird historical oddity, and just avoid it?

Well, generally, yes. However, there are a few cases where using the constructor to produce an array with a defined length works really well:

  1. Repeat

The fill method on Array will repeat an element for each element in the array, and doesn't care if it's a hole or not:

Array(4).fill("Bob")
// ["Bob", "Bob", "Bob", "Bob"]
  1. Ranges

The keys method returns an iterator for indexes of each element, effectively producing a range of numbers.

for (let num of Array(4).keys()) {
  console.log(num)
}
// 0
// 1
// 2
// 3

Note that an iterator isn't an Array; it's an object that you can call next on. It works with for...of and the spread operator:

[...Array(4).keys()]
// <- [0, 1, 2, 3]

Array.from

Unfortunately, with new functions introduced in ES6, holes are treated even more inconsistently. Here's a great summary.

Maybe a better alternative is to avoid the Array constructor, and use the new Array.from function. It takes anything that looks like an array, and an optional map function, and produces a real array. An object "looks" like an array if it has a length and allows element lookup by using []. An object literal with a length property does this just fine:

// No holes here.
Array.from({length: 3})
// [undefined, undefined, undefined]

We can produce very similarly compact code for repeat and range:

// Repeat.
Array.from({length: 4}, () => 'Bob')
// ["Bob", "Bob", "Bob", "Bob"]
// Ranges.
Array.from({length: 10}, (_, i) => i)
// [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

Conclusion

Holes are best avoided in JavaScript, since whether a method sees them, ignores them or just preserves them is very inconsistent. However, using them in conjunction with the Array constructor allows for some expressive and compact constructs. A better approach may be to use Array.from.

Latest Articles by Our Team

Our expert team of designers and developers love what the do and enjoy sharing their knowledge with the world.

We Hire Only the Best

reinteractive is Australia’s largest dedicated Ruby on Rails development company. We don’t cut corners and we know what we are doing.

We are an organisation made up of amazing individuals and we take pride in our team. We are 100% remote work enabling us to choose the best talent no matter which part of the country they live in. reinteractive is dedicated to making it a great place for any developer to work.

Free Community Workshops

We created the Ruby on Rails InstallFest and Ruby on Rails Development Hub to help introduce new people to software development and to help existing developers hone their skills. These workshops provide invaluable mentorship to train developers, addressing key skills shortages in the industry. Software development is a great career choice for all ages and these events help you get started and skilled up.

  • Webinars

    Webinars

    Webinars are our online portal for tips, tricks and lessons learned in everything we do. Make the most of this free resource to help you become a better developer.

    Learn more about webinars

  • Installfest

    Installfest

    The Ruby on Rails Installfest includes a full setup of your development environment and step-by-step instructions on how to build your first app hosted on Heroku. Over 1,800 attendees to date and counting.

    Learn more about Installfest

  • Development Hub

    Development Hub

    The Ruby on Rails Development Hub is a monthly event where you will get the chance to spend time with our team and others in the community to improve and hone your Ruby on Rails skills.

    Learn more about Development Hub

Get the “reinteractive Review” Monthly Email