Array Size in JavaScript: A Visual Guide
You have an array of four scores and need to know its size. scores.length returns 4, but that answer changes meaning once the array contains empty slots or you need to count only scores that passed.
Use
array.lengthfor an array’s index range, count occupied slots separately when holes matter, and use a concrete callback such asfilter(score => score >= 10).lengthwhen size means values matching a condition.
How to Get an Array’s Size
For an ordinary JavaScript array, read its length property. There are no parentheses because length is a property, not a method:
const emptyScores = [];
const scores = [18, 12, 20, 15];
console.log(emptyScores.length);
console.log(scores.length);
0
4
The empty array has a length of 0. The second array has four values at indexes 0, 1, 2 and 3, so its length is 4.
JavaScript arrays do not provide a standard .size property or .size() method. Reading a missing property produces undefined, while trying to call it as a function throws:
const scores = [18, 12, 20, 15];
console.log(scores.size);
// scores.size(); // TypeError
undefined
Use this form:
const size = scores.length;
Not these:
const size = scores.size;
const size = scores.size();
For a dense array, where every index from 0 through the final index contains an element, length also tells you how many elements the array holds. That familiar case makes the rule look simpler than it is.
Holes change the answer.
Arrays covers the wider set of operations for adding, removing and transforming values. Here, the important part is deciding what size means before choosing the expression that calculates it.
What Array Length Actually Counts
An array’s length describes its index range. It is normally one greater than the highest array index.
Indexes start at zero, so three values occupy indexes 0, 1 and 2:
const cities = ['Lima', 'Oslo', 'Kyoto'];
console.log(cities[0]);
console.log(cities[2]);
console.log(cities.length);
Lima
Kyoto
3
The last index is 2, but the length is 3. That one-step difference comes from zero-based indexing.
Assigning a value beyond the current end increases the length automatically. The indexes between the previous end and the new value become empty slots:
const cities = ['Lima', 'Oslo', 'Kyoto'];
cities[5] = 'Accra';
console.log(cities.length);
console.log(cities[5]);
console.log(3 in cities);
console.log(4 in cities);
6
Accra
false
false
Writing index 5 makes the length 6, because the range now runs from index 0 through index 5. Nothing was written at indexes 3 and 4.
That gives the first of three useful meanings for array size:
- Index-range length is
array.length, normally one greater than the highest index. - Occupied slots counts indexes where an element is present.
- Matching values counts elements that satisfy a condition, such as scores of at least
10.
A dense array gives the same number for the first two meanings. A sparse array, which has one or more empty slots inside its length, does not.
Array length is not a fixed capacity. You can add an element beyond the current end, reduce length, or increase length later. The array’s index range changes with those operations.
This distinction also matters when counting in JavaScript: the correct counter follows the thing being counted, not the name you happen to give the result.
Length, Empty Slots, and Undefined Values
An array position can be in three distinct states:
- An occupied slot with a value has an element such as
0,'ready'orfalse. - An occupied slot containing
undefinedexists, and its stored value isundefined. - An empty slot, also called a hole, has no element at that index.
Use one sparse array to put all three states next to each other:
| Index | 0 | 1 | 2 | 3 | 4 | 5 |
|---|---|---|---|---|---|---|
| Slot | occupied | hole | occupied | occupied | hole | occupied |
| Value | 12 | — | undefined | 0 | — | 18 |
const readings = Array(6);
readings[0] = 12;
readings[2] = undefined;
readings[3] = 0;
readings[5] = 18;
console.log(readings.length);
console.log(1 in readings, readings[1]);
console.log(2 in readings, readings[2]);
console.log(3 in readings, readings[3]);
6
false undefined
true undefined
true 0
Index 1 is empty. Index 2 exists and contains undefined. Index 3 exists and contains the ordinary, falsy numeric value 0.
The expression index in array tests whether a property exists at that index. Reading indexes 1 and 2 produces undefined in both cases, but the in checks reveal the difference. The first index is missing; the second is present.
That difference changes the counts:
const readings = Array(6);
readings[0] = 12;
readings[2] = undefined;
readings[3] = 0;
readings[5] = 18;
const occupiedSlots = readings.filter(() => true).length;
const definedValues = readings.filter(
value => value !== undefined
).length;
console.log(readings.length);
console.log(occupiedSlots);
console.log(definedValues);
3
2
1
The index-range length is 3. There are two occupied slots, because indexes 1 and 2 are present. There is one defined value, because the explicit undefined at index 1 does not pass the second filter.
filter() does not call its predicate for empty slots. That is why filter(() => true) keeps every present element, including the explicit undefined, while skipping the hole.
Increasing length creates the same kind of empty slot. It does not write undefined into each new position:
const readings = [14];
readings.length = 3;
console.log(readings.length);
console.log(1 in readings);
console.log(2 in readings);
3
false
false
The array now covers three indexes, but only index 0 is occupied. This is why indexed access alone cannot tell a hole from an explicit undefined.
The distinction fits JavaScript’s broader data types: undefined is a value. A hole is the absence of an indexed element.
Count the Thing You Actually Mean
Start by naming the count. The same array can have a length of 6, four occupied slots and different matching-value counts: three for value !== undefined, or two for the passing rule.
The same readings array contains all three slot states and both example rules:
const readings = Array(6);
readings[0] = 12;
readings[2] = undefined;
readings[3] = 0;
readings[5] = 18;
const indexRange = readings.length;
const occupiedSlots = readings.filter(() => true).length;
const valuesNotUndefined = readings.filter(
value => value !== undefined
).length;
const passingValues = readings.filter(
value => typeof value === 'number' && value >= 10
).length;
console.log(indexRange);
console.log(occupiedSlots);
console.log(valuesNotUndefined);
console.log(passingValues);
6
4
3
2
Indexes 1 and 4 are holes. Index 2 is occupied but contains undefined. Index 3 contains 0, which is a defined number but does not meet the passing rule.
The recipe follows the question:
| What “size” means | Recipe | Result for readings |
|---|---|---|
| Index range | readings.length | 6 |
| Occupied slots | readings.filter(() => true).length | 4 |
| Values matching the passing rule | readings.filter(value => typeof value === 'number' && value >= 10).length | 2 |
The last recipe is the one to use for questions such as “How many tasks are complete?” or “How many prices are below 20?” Its callback states the rule, and .length counts the filtered result. Another rule, such as value => value !== undefined, produces a different matching-value count.
filter(Boolean) is not a general element counter. It removes every value that converts to false, including valid values such as 0, false, NaN, null and an empty string:
const responses = [0, false, '', null, NaN, 'saved'];
console.log(responses.length);
console.log(responses.filter(Boolean).length);
console.log(
responses.filter(value => value !== undefined).length
);
6
1
6
All six slots are occupied, and none contains undefined. filter(Boolean) returns only 'saved', so it answers “How many values are truthy?” That is a different question.
A loop is useful when you need the count without creating a filtered array, or when counting is one part of a larger pass through the data. Loops: while and for develops that pattern from the first iteration onward.
Create an Array of a Given Size
Array(n) creates an array with a length of n and n empty slots. It does not create n elements containing undefined:
const reserved = Array(3);
console.log(reserved.length);
console.log(0 in reserved);
console.log(1 in reserved);
console.log(2 in reserved);
3
false
false
false
Use Array(n) when an empty index range is genuinely what you need. If later code expects initialized values, choose a creation method that writes them.
fill(value) places a value into every slot:
const flags = Array(3).fill(false);
console.log(flags.length);
console.log(0 in flags);
console.log(flags.join(', '));
3
true
false, false, false
All three positions now exist and contain false. Repeated primitive values such as numbers, strings and booleans work cleanly with fill().
Objects need more care. fill({}) repeats one object reference rather than creating a separate object for each index:
const rows = Array(3).fill({ status: 'waiting' });
rows[0].status = 'done';
console.log(rows[0].status);
console.log(rows[1].status);
console.log(rows[2].status);
console.log(rows[0] === rows[1]);
done
done
done
true
Changing the object through rows[0] is visible through every slot because all three point to the same object.
Use Array.from() with a mapping function when each position needs its own value:
const rows = Array.from(
{ length: 3 },
(_, index) => ({
id: index + 1,
status: 'waiting',
})
);
rows[0].status = 'done';
console.log(rows[0].status);
console.log(rows[1].status);
console.log(rows[2].status);
console.log(rows[0] === rows[1]);
done
waiting
waiting
false
The mapping function runs for every index. Each call returns a new object, so changing the first row leaves the other two alone.
The same form can create numbers derived from their indexes:
const pageNumbers = Array.from(
{ length: 5 },
(_, index) => index + 1
);
console.log(pageNumbers.join(', '));
1, 2, 3, 4, 5
Unlike Array(5), this result has five occupied slots. The values are initialized as the array is created.
The choice is compact:
- Use
Array(n)for an index range containing empty slots. - Use
Array(n).fill(primitive)for one repeated primitive value. - Use
Array.from({ length: n }, mapper)for per-index values or separate objects.
These creation patterns sit next to array transformations and other core language tools in JavaScript Fundamentals.
Changing Length and Avoiding Common Mistakes
Assigning a smaller number to length truncates the array. Elements at indexes outside the new range are deleted:
const queue = ['draft', 'review', 'publish', 'archive'];
queue.length = 2;
console.log(queue.length);
console.log(queue.join(', '));
console.log(2 in queue);
2
draft, review
false
The array keeps indexes 0 and 1. The values at indexes 2 and 3 are gone.
Assigning 0 clears every indexed element:
const queue = ['draft', 'review'];
queue.length = 0;
console.log(queue.length);
console.log(queue[0]);
0
undefined
Increasing length does the opposite to the range, but it does not restore deleted values or initialize new ones. It creates empty slots.
A valid array length is a nonnegative integer below 2 ** 32. The language-level maximum is 4,294,967,295, as documented in MDN’s Array: length reference, but that number is not a promise that a machine can hold a useful array of that size. Available memory and runtime limits can be lower.
Negative numbers, fractions and 2 ** 32 are invalid lengths:
for (const length of [-1, 1.5, 2 ** 32]) {
try {
Array(length);
} catch (error) {
console.log(error.name);
}
}
RangeError
RangeError
RangeError
Finally, keep collection terminology separate. Arrays use .length, while Set and Map use .size:
const tags = ['js', 'arrays', 'js'];
const uniqueTags = new Set(tags);
const visits = new Map([
['home', 12],
['docs', 8],
]);
console.log(tags.length);
console.log(uniqueTags.size);
console.log(visits.size);
3
2
2
The array has three positions. The Set has two elements after removing the repeated 'js' value, and the Map has two entries. Same word in conversation, different properties in code.