completefrontend
Open lab My progress
THE JAVASCRIPT FIELD GUIDE

Fast arrays & elements kinds

Learn how V8 classifies JavaScript arrays as packed, holey, Smi, double, generic, or dictionary elements, and what makes them slow.

By the end, you can
  • 01
    Read an array's labelTell a packed array from a holey one, and explain why the label only moves from small whole numbers to any numbers to any values.
  • 02
    Spot slow array codeFind holes, far-apart indexes, reads past the end, and array-like objects before you blame normal arrays.
  • 03
    Check engine facts with careRun Node-only tests that show an array's label and spare room, and keep them apart from what JavaScript itself promises.

Arrays keep their numbered values in a separate store

An array can hold any mix of values: numbers, strings, and objects. It can even have empty places. That makes arrays very flexible.

The engine is the program that runs your JavaScript. Chrome and Node use an engine called V8. V8 wants to read list[0] fast. So it keeps an array's numbered values in their own store, apart from named properties like list.name.

  • The numbered values are called elements.
  • The number in the square brackets, like the 0 in list[0], is called the index.
Definition

An elements kind is a label V8 puts on an array's elements. Your code can't see it. The label answers two questions:

  • Is any index missing? Packed means no. Holey means yes.
  • What values are stored? Small whole numbers, any numbers, any values, or a few far-apart indexes.

V8 uses the label to pick the fastest way to read and write the elements.

Two kinds of facts appear in this lesson. Keep them apart:

  • Language rules are what your code can see, like an array's values and its length. They are the same in every engine.
  • Engine facts are how V8 stores arrays inside. Your code can't see them, and they can change between versions. The lesson checks them in Node 22, which uses V8 12.4.

This lesson follows the V8 team's article Elements kinds in V8. The Hidden classes lesson covered named properties. This one covers numbered elements. For how arrays behave in normal code, see array basics, iterating arrays, and typed arrays.

Real-life analogyAn egg tray

A full tray of same-size eggs is easy to count and carry. An empty cup, a bigger egg, or a toy in the tray means you have to look more carefully.

In real life: Every cup in the tray has an egg
In JavaScript: A packed array of small whole numbers, like [1, 2, 3]
In real life: One cup is empty
In JavaScript: A holey array: each read must check first
In real life: A bigger egg goes in
In JavaScript: A decimal like 2.5: the array now holds any numbers
In real life: A toy goes in
In JavaScript: A string or object: the array now holds any values

Packed arrays and holes

An array is packed when every index from 0 to length - 1 has a value. It is holey when at least one of those indexes has nothing at all. An index with nothing is called a hole.

A hole is not the same as undefined. After arr[0] = undefined, index 0 exists and holds the value undefined. A hole means index 0 does not exist at all.

The code below uses the in operator. 0 in arr is true when index 0 exists in arr, and false when it doesn't.

A packed array beside a holey arrayPop out in the code editor (opens in a new tab)JavaScript
const packed = ["Asha", "Ravi", "Meera"];const holey = new Array(3);holey[1] = "Ravi"; console.log(0 in packed);console.log(0 in holey);console.log(holey.length);
  • Line 1 makes packed with three values, at indexes 0, 1, and 2.
  • Line 2 makes holey with new Array(3). This sets the length to 3 but puts no values in. All three indexes are holes.
  • Line 3 puts "Ravi" at index 1. Indexes 0 and 2 are still holes.
  • Line 5 prints true: index 0 exists in packed.
  • Line 6 prints false: index 0 is a hole in holey.
  • Line 7 prints 3. The length counts the holes too.
Left: the packed array holds Asha at index 0, Ravi at index 1, and Meera at index 2. Right: the holey array made with new Array(3) holds only Ravi at index 1. Indexes 0 and 2 are holes.["Asha", "Ravi", "Meera"]012AshaRaviMeerapackedevery index has a valuenew Array(3)holey[1] = "Ravi"012holeholeRaviholeyindexes 0 and 2 are holes
In packed, every index from 0 to 2 has a value. In holey, only index 1 has a value, so indexes 0 and 2 are holes.

Why does V8 care? When it reads from a packed array, it knows a value is there. When it reads from a holey array, it must check first.

If the index is a hole, JavaScript keeps looking in the array's prototype. That is the object the array gets its methods from, Array.prototype. This extra search costs time on every read.

V8 also remembers that an array was holey. If you fill the holes later, the label stays holey. It does not switch back to packed.

The table lists the labels this lesson uses. Read each row as: the label, how an array gets it, and what it means for speed.

The labels V8 uses for arrays
LabelHow an array gets itWhat it means
PACKED_SMI_ELEMENTSNo holes, and every value is a small whole number, like [1, 2, 3].The fastest label for whole numbers.
PACKED_DOUBLE_ELEMENTSNo holes, and at least one value is a decimal, -0, NaN, or Infinity.Still fast. V8 stores every value as a plain number.
PACKED_ELEMENTSNo holes, and at least one value is not a number, like a string or an object.V8 can hold any value, but it knows less about each one.
HOLEY_SMI_ELEMENTS and the other HOLEY_ labelsAt least one index is a hole.Every read must first check for a hole, and maybe search the prototype.
DICTIONARY_ELEMENTSOnly a few indexes exist, far apart, like one value at index one million.V8 keeps a table of the indexes that exist. Reads are slower, but no memory is spent on holes.

Small whole numbers, any numbers, or any values

The second part of the label says what values the array holds. V8 has three groups:

  • Smi, short for "small integer": small whole numbers like 1, 2, and 3. V8 can store these straight in the array with no extra work.
  • Double: any number, including decimals like 2.5. "Double" is the name of the number format JavaScript uses for all its numbers.
  • Generic: any value at all, such as strings, objects, and numbers mixed together. V8's label for this group has no middle word: PACKED_ELEMENTS or HOLEY_ELEMENTS.

Some numbers look simple but are not Smis:

  • -0 is negative zero. JavaScript keeps it apart from 0, so V8 can't store it as a Smi.
  • NaN means "not a number". It is the result of maths that fails, like 0 / 0.
  • Infinity is bigger than every other number.
Numbers that need the double groupPop out in the code editor (opens in a new tab)JavaScript
const counts = [1, 2, 3];counts.push(-0);counts.push(NaN);counts.push(Infinity); console.log(Object.is(counts[3], -0));console.log(Number.isNaN(counts[4]));console.log(counts[5] === Infinity);
  • Line 1 starts with three Smis.
  • Line 2 pushes -0. push adds a value at the end of the array.
  • Lines 3 and 4 push NaN and Infinity. All three new values need the double group.
  • Line 6 prints true. Object.is(a, b) checks whether two values are exactly the same, and it can tell -0 from 0.
  • Line 7 prints true. Number.isNaN(x) is true only when x is NaN.
  • Line 8 prints true: counts[5] is Infinity.

Normal JavaScript can't show the label. Node can, with special V8 test functions whose names start with %. For example, %HasSmiElements(arr) returns true when arr has a Smi label.

These functions only work when you start Node with node --allow-natives-syntax. They are not part of JavaScript, so never use them in a real app.

Node only: ask V8 for an array's labelJavaScript
function label(array) {  if (%HasDictionaryElements(array)) return "DICTIONARY_ELEMENTS";  const prefix = %HasHoleyElements(array) ? "HOLEY" : "PACKED";  if (%HasSmiElements(array)) return prefix + "_SMI_ELEMENTS";  if (%HasDoubleElements(array)) return prefix + "_DOUBLE_ELEMENTS";  if (%HasObjectElements(array)) return prefix + "_ELEMENTS";  return "OTHER_ELEMENTS";} function report(name, array) {  console.log(name + ": " + label(array));} console.log("Node " + process.version);console.log("V8 " + process.versions.v8.split(".").slice(0, 2).join("."));const values = [1, 2, 3];report("packed smi", values);values.push(4.5);report("after double", values);values.push("x");report("after string", values);values.pop();values.pop();report("after popping back", values);const holey = new Array(3);report("new Array", holey);holey[0] = 1;holey[1] = 2;holey[2] = 3;report("filled holey", holey);const short = [1, 2, 3];short[10] = 1;report("write index 10", short);const minusZero = [1, 2, 3];minusZero.push(-0);report("minus zero", minusZero);const notANumber = [1, 2, 3];notANumber.push(NaN);report("NaN", notANumber);const infinity = [1, 2, 3];infinity.push(Infinity);report("Infinity", infinity);const sparse = [];sparse[1e6] = 1;report("sparse", sparse);
  • Lines 1 to 8 make a function, label, that asks V8 for an array's label. It checks for a dictionary first, then for holes, then for each group of values.
  • Lines 10 to 12 make report, which prints a name and the label.
  • Lines 14 and 15 print the Node and V8 versions.
  • Line 17 prints packed smi: PACKED_SMI_ELEMENTS.
  • Line 18 pushes 4.5. Line 19 prints after double: PACKED_DOUBLE_ELEMENTS.
  • Line 20 pushes "x". Line 21 prints after string: PACKED_ELEMENTS.
  • Lines 22 and 23 take both new values off again. pop removes the last value. Line 24 still prints after popping back: PACKED_ELEMENTS.
  • Line 26 prints HOLEY_SMI_ELEMENTS for new Array(3). Lines 27 to 29 fill every hole, but line 30 still prints HOLEY_SMI_ELEMENTS.
  • Line 32 writes index 10 into a three-value array. Line 33 prints HOLEY_SMI_ELEMENTS: indexes 3 to 9 are holes.
  • Lines 34 to 42 push -0, NaN, and Infinity into new arrays. Each prints PACKED_DOUBLE_ELEMENTS.
  • Line 44 writes index 1e6, which means one million, into an empty array. Line 45 prints sparse: DICTIONARY_ELEMENTS. A later section explains this label.
What the lesson test saw

The lesson test runs this code in Node 22, which uses V8 12.4. It checks every printed label, and they match the list above.

How big can a Smi be? That depends on how V8 was built. One build setting is called pointer compression. It saves memory, and Chrome usually turns it on. Node 22 turns it off.

With pointer compression off, the biggest Smi is 2 ** 31 - 1, which is 2,147,483,647. The ** operator means "to the power of".

Node only: the biggest Smi in this NodeJavaScript
console.log("Node " + process.version);console.log("V8 " + process.versions.v8.split(".").slice(0, 2).join("."));console.log("pointer compression", process.config.variables.v8_enable_pointer_compression);console.log("2**31 - 1", %IsSmi(2 ** 31 - 1));console.log("2**31", %IsSmi(2 ** 31));
  • Lines 1 and 2 print the Node and V8 versions.
  • Line 3 prints pointer compression 0. The 0 means the setting is off.
  • Line 4 prints 2**31 - 1 true. %IsSmi(n) says whether V8 can store n as a Smi.
  • Line 5 prints 2**31 false. One more is too big.
Node and Chrome can differ

The test checks Node 22, V8 12.4, pointer compression 0, and the two answers true and false. In Chrome the biggest Smi can be smaller. So don't write code that depends on this number.

A label only moves one way

When you add a value that doesn't fit the current label, V8 moves the array to a label that holds more types of values. It only moves in this order:

  • Smi, then double, then generic.
  • Packed, then holey.

V8 does not move a label back. To do that, it would have to check every value in the array again after each change, and that would cost more time than it saves.

Step through how an array's kind changes
Step 0 of 9Ready
Your turn: follow the blue line

Follow one array's label as values go in and come out. This replay follows V8's rules, and the lesson test checks them in real Node.

Running in
  1. script
Next: line 1
fares.push(4.5);fares.push("₹");fares.pop();fares.pop();console.log(fares.join(","));console.log(fares.length);
CallStoreChangeResultBlue line: runs next. Ran: already executed.
Recent returnsNothing yet. Start with the blue line.
A guided replay recorded from real JavaScript calls, not an engine debugger. Step follows executed statements; Back reviews a snapshot. Reset starts a fresh run.
  • Line 1 starts fares with small whole numbers. Its label is PACKED_SMI_ELEMENTS.
  • Line 2 pushes 4.5, a decimal. The label moves to PACKED_DOUBLE_ELEMENTS.
  • Line 3 pushes the string "₹", the rupee sign. The label moves to PACKED_ELEMENTS.
  • Lines 4 and 5 pop the string and the decimal off again.
  • Line 6 prints 1,2,3 and line 7 prints 3. The values look like the start again, but V8 keeps the PACKED_ELEMENTS label. The Node test earlier proves this on its lines 22 to 24.
The fares array starts as [1, 2, 3], kind PACKED_SMI_ELEMENTS. After push(4.5) it is PACKED_DOUBLE_ELEMENTS. After push of the rupee string it is PACKED_ELEMENTS. After two pops it is [1, 2, 3] again, but still PACKED_ELEMENTS.[1, 2, 3]PACKED_SMI_ELEMENTSsmall whole numberspush(4.5)[1, 2, 3, 4.5]PACKED_DOUBLE_ELEMENTSany numberspush("₹")[1, 2, 3, 4.5, "₹"]PACKED_ELEMENTSany kind of valuepop() twice[1, 2, 3]PACKED_ELEMENTSdoes not go back
Each push can move fares to a kind that holds more types of values. After the two pops the values look the same as at the start, but the kind stays PACKED_ELEMENTS.

%DebugPrint(value) is another V8 test function. It prints everything V8 knows about a value, and one of its lines is the label.

Node only: print what V8 knows about an arrayJavaScript
const arr = [1, 2, 3];%DebugPrint(arr);
What the lesson test saw

In Node 22, %DebugPrint(arr) for [1, 2, 3] prints the line elements kind: PACKED_SMI_ELEMENTS. The rest of the output, like memory addresses, changes from run to run, so the test only checks that line.

Playground: guess an array's kind
What you did to the arrayJavaScript
const model = createElementsKindModel();model.push(1);       // PACKED_SMI_ELEMENTSmodel.push(2.5);     // PACKED_DOUBLE_ELEMENTSmodel.push("₹");     // PACKED_ELEMENTSmodel.writePastEnd(); // HOLEY_ELEMENTSmodel.push(-0);      // still HOLEY_ELEMENTS
Kind nowPACKED_SMI_ELEMENTS
PACKED_SMI_ELEMENTS current kind0 visible lengthno has holesno dictionary

No actions yet. Start with `Push 1`, then compare `Push 2.5`, `Push ₹`, and `Make a hole`.

Step 0 of 5PACKED_SMI_ELEMENTS

Start with an empty array of small integers. Add values or make a hole, and watch the kind move down the list. A kind never moves back up.

This is a model of V8's rules. It does not look inside your browser's engine. The lesson test checks the same steps against V8 in Node.

The playground does not look inside your browser. It follows V8's rules in a small model written for this lesson. The lesson test runs the same steps in Node with the % functions and checks that the labels match.

Very spread-out arrays become dictionaries

An array is sparse when most of its indexes are holes. A few holes are fine: V8 keeps a holey list. But if you write one value at index one million, a list would need a million empty places first.

So V8 switches to a dictionary. A dictionary is a table that keeps only the indexes that exist, each with its value. V8's label for it is DICTIONARY_ELEMENTS. Reading from a dictionary is slower than reading from a list, because V8 has to look the index up in the table.

Real-life analogyA phone book and a contact list

A phone book has a line for every number. Your phone's contact list only saves the numbers you use. When you use only a few far-apart numbers, the contact list is much smaller.

In real life: A phone book prints a line for every number, in order
In JavaScript: A normal list: one place for every index
In real life: You only call one person, at number 1,000,000
In JavaScript: sparse[1_000_000] = "Jaipur"
In real life: Printing a million blank lines first is a waste
In JavaScript: A normal list would need a million holes
In real life: Your contact list saves just that one name and number
In JavaScript: A dictionary: only the indexes that exist, each with its value
One value far from the startPop out in the code editor (opens in a new tab)JavaScript
const sparse = [];sparse[1_000_000] = "Jaipur"; console.log(sparse.length);console.log(0 in sparse);console.log(1_000_000 in sparse);
  • Line 1 makes an empty array, sparse.
  • Line 2 writes "Jaipur" at index 1_000_000. The underscores only make the number easier to read. It is one million.
  • Line 4 prints 1000001. The length is always the highest index plus one.
  • Line 5 prints false: index 0 is a hole.
  • Line 6 prints true: index one million exists.
The code sparse[1_000_000] = Jaipur. Left: a plain list would need 1,000,000 holes before Jaipur. Right: a dictionary keeps one entry, the key 1000000 pointing to Jaipur.sparse[1_000_000] = "Jaipur"plain list1,000,000 holesJaipuralmost all emptydictionary1000000Jaipurone entry
A plain list would need a million empty places before "Jaipur". A dictionary keeps only the one entry that exists: the index and its value.
What the lesson test saw

Lines 43 to 45 of the Node test code earlier do the same thing. They make const sparse = [], then write sparse[1e6] = 1. In Node 22, V8 reports DICTIONARY_ELEMENTS for it.

If your numbers are really IDs, not places in a list, an array is the wrong tool. A Map is a built-in object that stores pairs of a key and a value. A Map or a plain object says what you mean, and it has no million-long gap.

Arrays keep spare room

An array's length is the number your code sees. Behind it, V8 keeps a store with room for the elements:

  • This store is called the backing store.
  • The number of places in it is its capacity.

Making a bigger store means copying every value into it. If V8 did that on every push, adding values would be slow. So when the store is full, V8 makes a bigger one with spare places. The next pushes go into the spare places, with no copying.

Step through how the array's storage grows
Step 0 of 6Ready
Your turn: follow the blue line

Follow the length your code sees and the capacity V8 sets aside, as values are pushed into an empty array.

Running in
  1. script
Next: line 1
orders.push(1);for (let ticket = 2; ticket <= 18; ticket += 1) {  orders.push(ticket);}orders.push(19);console.log(orders.length);
CallStoreChangeResultBlue line: runs next. Ran: already executed.
Recent returnsNothing yet. Start with the blue line.
A guided replay recorded from real JavaScript calls, not an engine debugger. Step follows executed statements; Back reviews a snapshot. Reset starts a fresh run.
  • Line 1 starts with an empty array: length 0 and capacity 0.
  • Line 2 pushes 1. V8 makes a store with 17 places.
  • Lines 3 to 5 push 2 to 18. The 18th value doesn't fit in 17 places, so V8 makes a new store with 43 places.
  • Line 6 pushes 19. It fits, so the capacity stays 43.
  • Line 7 prints 19. Your code only ever sees the length.
After the first push, orders has 1 value and 16 spare slots: length 1, capacity 17. After pushes 2 to 18, it has 18 values and 25 spare slots: length 18, capacity 43.116 spare slotslength 1capacity 17push 2 to 181 to 1825 sparelength 18capacity 43
length counts the values. Capacity counts the room V8 has set aside, which includes spare slots. When the room runs out, V8 makes a bigger store with new spare slots.

How does V8 pick 17 and 43? Its source code has a function, NewElementsCapacity, that takes the number of places needed now, adds half of it, and then adds 16 more. Here is that rule written in JavaScript:

V8's rule for the new capacityPop out in the code editor (opens in a new tab)JavaScript
function newElementsCapacity(requestedCapacity) {  return requestedCapacity + (requestedCapacity >> 1) + 16;} console.log(newElementsCapacity(1));console.log(newElementsCapacity(18));console.log(newElementsCapacity(44));
  • Line 1 takes requestedCapacity: the number of places needed now, which is the new length.
  • Line 2 is the whole rule. requestedCapacity >> 1 halves the number and drops any remainder, so 18 >> 1 is 9.
  • Line 5 prints 17: 1 + 0 + 16.
  • Line 6 prints 43: 18 + 9 + 16.
  • Line 7 prints 82: 44 + 22 + 16.

The table shows the three moments when the store grew. Read each row from left to right. The test checked each capacity with %DebugPrint.

When the store grew
WhenLengthCapacityWhy
After the first push117The store was empty. V8 needs 1 place, so it makes 1 + 0 + 16 = 17.
After push 181843The 18th value doesn't fit in 17 places. V8 makes 18 + 9 + 16 = 43.
After push 444482The 44th value doesn't fit in 43 places. V8 makes 44 + 22 + 16 = 82.
Don't depend on these numbers

17, 43, and 82 are true for V8 12.4. Another version can pick other numbers. What stays true is that arrays grow with spare room. Don't write app code that depends on an exact capacity.

Array-like objects are not arrays

An array-like object has number keys and a length, but it is not a real array. Common examples:

  • arguments: an object inside every normal function (not arrow functions). It holds the values the function was called with.
  • Older browser lists, like the one document.getElementsByTagName("p") returns.
  • Objects you build yourself, like { 0: "Asha", length: 1 }.

Array.isArray(value) tells you whether a value is a real array. Most array-likes have no array methods such as forEach. You can borrow one with .call: Array.prototype.forEach.call(list, fn) runs forEach with list as the array it works on. It works, but V8's fast paths are made for real arrays.

Borrowing an array method, then making a real arrayPop out in the code editor (opens in a new tab)JavaScript
function logArguments() {  console.log(Array.isArray(arguments));  console.log(arguments.length);} logArguments("Asha", "Ravi"); const list = { 0: "Asha", 1: "Ravi", length: 2 };Array.prototype.forEach.call(list, (name, index) => {  console.log(index + ":" + name);});console.log(Array.from(list).join("|"));
  • Lines 1 to 4 make logArguments, which prints two facts about arguments.
  • Line 6 calls it with "Asha" and "Ravi". Line 2 prints false: arguments is not an array. Line 3 prints 2.
  • Line 8 makes list, an object with the keys 0 and 1 and a length of 2.
  • Lines 9 to 11 borrow forEach with .call. They print 0:Asha and 1:Ravi.
  • Line 12 uses Array.from(list), which builds a real array from list. Then join("|") prints Asha|Ravi.

The table compares a real array with two kinds of array-like values. Read each row from left to right.

Real arrays and array-like objects
ValueWhat it isWhat to do
A real arrayArray.isArray(value) is true, and it has every array method.Use it as it is. V8's fast paths are made for it.
argumentsIt holds a function's values and has a length, but it is not an array.Use rest parameters, (...args), to get a real array instead.
A browser list or your own objectIt looks like { 0: item, length: 1 }, but it is not an array.Call Array.from(list) once if you will run several array methods.
A simple rule

If you will run several array methods on an array-like, turn it into an array once with Array.from. Inside a function, use rest parameters, (...args), instead of arguments: they give you a real array. If you only loop once, write whatever is clearest.

Use this in real code, carefully

Elements kinds explain why some array code is slow. They are not a reason to write strange code. Work in this order:

  1. Write clear code with normal arrays.
  2. Measure the part users wait for. A profiler is a browser tool that shows where the time goes.
  3. If the profiler points at array code, try one change, such as removing holes. Then measure again.

One easy habit: check values once, where they come into your program. Then the array only ever gets the values you expect.

Check numbers before they go in the arrayPop out in the code editor (opens in a new tab)JavaScript
const readings = [];for (const value of [18, 19, 20]) {  if (Number.isFinite(value) && !Object.is(value, -0)) {    readings.push(value);  }} console.log(readings.join(","));
  • Line 1 starts with an empty array, readings.
  • Line 2 loops over the numbers 18, 19, and 20.
  • Line 3 lets a number in only if Number.isFinite(value) is true, so it is not NaN or Infinity, and it is not -0.
  • Line 4 pushes the number. Line 5 closes the check.
  • Line 8 prints 18,19,20.

Habits that keep arrays on V8's fast path:

  • Build arrays with [...] or push. new Array(n) starts with holes, and the label stays holey.
  • Keep out -0, NaN, and Infinity only when your data really should be whole numbers.
  • Don't read past the end, like items[3] in a three-value array. V8 has to search the prototype for index 3, and the answer is just undefined.
  • Use a Map or an object for far-apart IDs. Use an array for places in a list.
  • For large lists of numbers, consider a typed array. A typed array, like Float64Array, holds only one type of number.
  • Measure before and after a change. V8's exact rules can change between versions.
Where does each array end up?
  • const a = [1, 2, 3];
  • const a = [1, 2, 3]; a.push(-0);
  • const a = new Array(3); a[0] = a[1] = a[2] = 1;
  • const a = [1, 2, 3]; a[10] = 1;
  • const a = []; a[1_000_000] = 1;
  • Array.prototype.forEach.call(arguments, fn);
Try it yourself
0 of 6 correct

Put each line of code in the group V8 gives it. Look for holes and far-apart indexes first. Then check whether it is a real array.

Choose a category for every card. You can change an answer at any time; Reset clears them all.

Common misconceptions

  • "A hole is just undefined." No. A hole is an index that doesn't exist, so 0 in array can be false. An undefined value is a value that does exist.
  • "Popping the odd value makes the array fast again." No. The values change, but V8 does not move the label back.
  • "Any gap turns an array into a dictionary." No. A few holes give a holey list. The lesson shows a dictionary only for one value at index one million.
  • "Elements kinds are JavaScript types." No. They are labels inside V8. Other engines store arrays in their own ways, with their own names.
  • "Knowing the engine means rewriting all my arrays." No. Write clear code, then measure. Change array code only where a profiler shows it is slow.

The table lists four pairs of ideas that are easy to mix up. Read each row from left to right.

Ideas that are easy to mix up
IdeaWhat it meansWhat it is not
A holeAn index that doesn't exist, so 0 in arr is false.It is not an index that holds the value undefined.
An elementA value stored at a number index in an array.It is not a named property. V8 keeps named properties in a separate place.
An array's lengthThe highest index plus one. Your code can read it.It is not the capacity. V8 keeps spare room your code can't see.
An array-like objectAn object with number keys and a length.It is not a real array, so most array methods are missing.

Practice exercises

Exercise 1 · Warm-upPredict what prints after two pops

Read the code and type the two lines it prints.

Starter codePop out in the code editor (opens in a new tab)JavaScript
const fares = [1, 2, 3];
fares.push(4.5);
fares.push("₹");
fares.pop();
fares.pop();
console.log(fares.join(","));
console.log(fares.length);

Answer, then press Check. Spacing and letter case don’t matter.

    Exercise 2 · Warm-upName the group for -0

    Which group of values does an array move to when you push -0: Smi, double, or generic?

    Answer, then press Check. Spacing and letter case don’t matter.

      Exercise 3 · PracticeRead a hole check

      What does line 2 print, before any value is put in?

      Starter codePop out in the code editor (opens in a new tab)JavaScript
      const seats = new Array(3);
      console.log(0 in seats);
      seats[0] = "Asha";
      console.log(seats[0]);

      Answer, then press Check. Spacing and letter case don’t matter.

        Exercise 4 · PracticeTurn an array-like into an array

        Which built-in function turns an array-like object into a real array, so you can run several array methods on it?

        Starter codePop out in the code editor (opens in a new tab)JavaScript
        const list = { 0: "Asha", 1: "Ravi", length: 2 };
        console.log(Array.from(list).join("|"));

        Answer, then press Check. Spacing and letter case don’t matter.

          Exercise 5 · PracticeFix the loop that reads past the end

          The code prints 60, but on its last turn it reads items[3], past the end. What loop condition should replace i <= items.length?

          Starter codePop out in the code editor (opens in a new tab)JavaScript
          const items = [10, 20, 30];
          let total = 0;
          for (let i = 0; i <= items.length; i += 1) {
            total += items[i] ?? 0;
          }
          console.log(total);

          Answer, then press Check. Spacing and letter case don’t matter.

            Exercise 6 · ChallengePick a home for far-apart IDs

            A site stores a few orders by their ID numbers, such as 42 and 1_000_000. What should you use instead of an array?

            Answer, then press Check. Spacing and letter case don’t matter.

              Check your understanding

              Elements kinds quiz · 8 questionsScore: first tries count
              1. Question 1 of 8What is an elements kind in V8?

                Choose an answer to see the explanation.

              2. Question 2 of 8What does this code print after writing index 2?

                Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
                const seats = [];
                seats[2] = "Ravi";
                console.log(seats.length);
                console.log(0 in seats);

                Choose an answer to see the explanation.

              3. Question 3 of 8Which value moves [1, 2, 3] from the Smi group to the double group?

                Choose an answer to see the explanation.

              4. Question 4 of 8An array has the label PACKED_ELEMENTS because of one string. What happens if you pop that string off?

                Choose an answer to see the explanation.

              5. Question 5 of 8What does this capacity rule print when 18 places are needed?

                Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
                function newElementsCapacity(requestedCapacity) {
                  return requestedCapacity + (requestedCapacity >> 1) + 16;
                }
                console.log(newElementsCapacity(18));

                Choose an answer to see the explanation.

              6. Question 6 of 8What does this borrowed forEach print?

                Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
                const list = { 0: "Asha", 1: "Ravi", length: 2 };
                Array.prototype.forEach.call(list, (name) => console.log(name));

                Choose an answer to see the explanation.

              7. Question 7 of 8When is V8 likely to use DICTIONARY_ELEMENTS?

                Choose an answer to see the explanation.

              8. Question 8 of 8Which habit fits what this lesson teaches?

                Choose an answer to see the explanation.

              Key takeaways

              • V8 keeps an array's numbered values, its elements, apart from named properties.
              • A packed array has a value at every index. A holey array has holes, so each read must check first.
              • The label moves from Smi to double to generic, and from packed to holey. It does not move back.
              • One value far from the start can turn an array into a dictionary. Array-like objects are not real arrays.
              • Arrays grow with spare room. The exact numbers belong to V8, so measure before you change code.

              In one line: arrays with no holes and one type of value are fast. Holes, mixed values, and far-apart indexes give V8 more work.

              Next, Functions & closures inside the engine looks at how V8 stores functions, and how it keeps the variables a closure still needs.

              CompleteFrontend Clear concepts. Working examples.