JavaScript Array Length: Count, Check, and Resize
You have four tasks in a list, so tasks.length returns 4. Then one task is deleted, the length still says 4, and the number that looked like a count turns out to be a boundary.
JavaScript’s
array.lengthproperty reports how many indexed slots an array spans; use it to check boundaries and resize arrays, but count occupied or matching values separately when holes orundefinedmatter.
Get an Array’s Length
Read an array’s size through its length property:
const tasks = ["plan", "build", "test"];
console.log(tasks.length);
3
There are no parentheses after length. It is a property containing a number, not a method you call.
For a dense array such as tasks, every position from 0 through 2 contains an element, so its length also happens to equal the number of stored values. That familiar case hides the more precise rule.
An array’s length is the number of indexed slots it spans.
A sparse array can contain empty slots, also called holes. Those slots still fall inside the array’s span, so length can be greater than the number of indexes that actually exist.
For an ordinary empty check, compare length with 0:
const waiting = [];
const completed = ["invoice sent"];
console.log(waiting.length === 0);
console.log(completed.length === 0);
true
false
This test asks whether the array spans zero slots. It does not ask whether every slot is empty or every stored value is undefined. That distinction matters once sparse arrays appear.
The Arrays guide covers array creation and common methods together. Here the focus stays on what length measures and what it does when you assign to it.
Length, Indexes, and the Last Item
JavaScript arrays use zero-based indexes. The first item is at index 0, the second is at index 1, and an array with length 3 has possible indexes from 0 through 2.
For a non-empty dense array, subtract 1 from the length to read the last item:
const stages = ["draft", "review", "published"];
console.log(stages.length);
console.log(stages[stages.length - 1]);
3
published
stages.length is 3, while the last index is 2. Using stages[stages.length] goes one position beyond the array and returns undefined.
The at() method expresses the same request with a negative index:
const stages = ["draft", "review", "published"];
console.log(stages.at(-1));
console.log(stages.at(-2));
published
review
at(-1) reads the final indexed position, and at(-2) moves back one more. An out-of-range index returns undefined.
Empty arrays therefore need no special guard when reading:
const stages = [];
console.log(stages[stages.length - 1]);
console.log(stages.at(-1));
undefined
undefined
The bracket expression becomes stages[-1]. That is an ordinary property lookup rather than an array index, and no such property exists here. at(-1) also has no indexed position to return.
There is one trap. length - 1 identifies the final possible index inside the array’s span, but that position can be a hole:
const scores = [8, 9, 10];
delete scores[2];
console.log(scores.length);
console.log(scores.at(-1));
console.log(Object.hasOwn(scores, scores.length - 1));
3
undefined
false
The array still spans three slots, but index 2 no longer exists. at(-1) returns undefined, and Object.hasOwn() reveals that the final position is a hole.
A conventional indexed loop uses length as its exclusive boundary:
const stages = ["draft", "review", "published"];
for (let index = 0; index < stages.length; index += 1) {
console.log(index, stages[index]);
}
0 draft
1 review
2 published
The condition is < stages.length, not <= stages.length. The Loops: while and for guide develops that boundary pattern further.
What Array Length Actually Counts
A dense array has an element at each index from 0 through length - 1. A sparse array has at least one hole inside that span.
A hole is not an element whose value is undefined. Indexed access produces undefined in both cases, but Object.hasOwn() separates them.
Start with one array containing an explicit undefined, a deleted element, and two trailing holes:
const readings = [18, undefined, 24, 12];
delete readings[2];
readings.length = 6;
let occupiedIndexes = 0;
for (let index = 0; index < readings.length; index += 1) {
if (Object.hasOwn(readings, index)) {
occupiedIndexes += 1;
}
}
const nonUndefinedValues =
readings.filter((value) => value !== undefined).length;
const readingsAtLeast15 =
readings.filter((value) => value >= 15).length;
console.log("slot span:", readings.length);
console.log("occupied indexes:", occupiedIndexes);
console.log("non-undefined values:", nonUndefinedValues);
console.log("values at least 15:", readingsAtLeast15);
for (let index = 0; index < readings.length; index += 1) {
console.log(
index,
Object.hasOwn(readings, index),
readings[index]
);
}
slot span: 6
occupied indexes: 3
non-undefined values: 2
values at least 15: 1
0 true 18
1 true undefined
2 false undefined
3 true 12
4 false undefined
5 false undefined
One array produces four correct counts because each count answers a different question.
Its length is 6, covering indexes 0 through 5. Only indexes 0, 1, and 3 exist as own properties, so the occupied-index count is 3.
Index 1 exists and explicitly contains undefined. Indexes 2, 4, and 5 are holes. Reading any of those four positions produces undefined, but their property checks differ.
Filtering out undefined leaves 18 and 12, for a count of 2. Filtering with value >= 15 leaves only 18, for a count of 1.
Sparse arrays also change iteration behavior. forEach() and reduce() skip holes, while for...of produces undefined for them. Spreading a sparse array creates a new array with explicit undefined values where the holes were.
That is why a hole cannot be treated as another spelling of undefined. Some operations expose the same read result, but the array structure is different. JavaScript forEach Loop: A Visual Guide shows how callback-based iteration visits array elements.
Choose the Count You Actually Need
The word “count” hides several questions. Name the question first, then choose the expression that answers it.
| Question | Recipe | Result for readings |
|---|---|---|
| How many indexed slots does the array span? | readings.length | 6 |
| How many indexes actually exist? | Indexed loop with Object.hasOwn(readings, index) | 3 |
How many visited values are not undefined? | readings.filter(value => value !== undefined).length | 2 |
| How many visited values match a condition? | readings.filter(value => value >= 15).length | 1 |
Use array.length when you need the array’s index boundary. Loop conditions, appending at the next index, and ordinary empty checks use that meaning.
Use an indexed loop with Object.hasOwn() when holes must not count but explicit undefined elements must:
function countOccupiedIndexes(array) {
let count = 0;
for (let index = 0; index < array.length; index += 1) {
if (Object.hasOwn(array, index)) {
count += 1;
}
}
return count;
}
const readings = [18, undefined, 24, 12];
delete readings[2];
readings.length = 6;
console.log(countOccupiedIndexes(readings));
3
This recipe examines only the indexes from 0 through length - 1. Object.hasOwn(array, index) returns true for an explicit undefined element and false for a hole.
Use filter() when the count depends on values. To exclude undefined, state that condition directly:
const readings = [18, undefined, 24, 12];
delete readings[2];
readings.length = 6;
const definedCount =
readings.filter((value) => value !== undefined).length;
console.log(definedCount);
2
filter() skips holes and tests the occupied elements it visits. The explicit undefined value fails the condition, while 18 and 12 pass after index 2 has been deleted.
For a business rule, put the rule in the predicate:
const readings = [18, undefined, 24, 12];
delete readings[2];
readings.length = 6;
const warmCount =
readings.filter((value) => value >= 15).length;
console.log(warmCount);
1
Only 18 remains to match. The deleted 24 is gone, the explicit undefined does not satisfy the comparison, and holes are not visited by filter().
How to Count in JavaScript covers counters, frequencies, and grouped totals beyond array length. The array chapter in JavaScript Fundamentals connects these recipes to the rest of the language when you want them in course order.
How Changing Length Changes the Array
The length property updates automatically as array methods add and remove items. Direct assignment at an index beyond the current boundary also grows it:
const queue = ["Maya", "Raj"];
queue.push("Lena");
console.log(queue.length, queue.join(", "));
queue.unshift("Noah");
console.log(queue.length, queue.join(", "));
queue.pop();
console.log(queue.length, queue.join(", "));
queue.shift();
console.log(queue.length, queue.join(", "));
queue[5] = "Iris";
console.log(queue.length);
console.log(Object.hasOwn(queue, 4));
console.log(queue[5]);
3 Maya, Raj, Lena
4 Noah, Maya, Raj, Lena
3 Noah, Maya, Raj
2 Maya, Raj
6
false
Iris
push() and unshift() increase the length. pop() and shift() decrease it. Writing index 5 makes the length 6, but it does not fill indexes 2, 3, and 4; those positions become holes.
Assigning a smaller length is destructive. When the affected indexed properties are configurable, JavaScript deletes every element at an index greater than or equal to the new length:
const steps = ["plan", "build", "test", "ship"];
steps.length = 2;
console.log(steps.length);
console.log(steps.join(", "));
console.log(steps[2]);
2
plan, build
undefined
The "test" and "ship" elements are gone. Restoring the old length later creates holes rather than restoring the deleted values.
When every indexed property is configurable, setting length to 0 clears every indexed element:
const steps = ["plan", "build", "test"];
steps.length = 0;
console.log(steps.length);
console.log(steps);
0
[]
Assigning a larger length takes the opposite path:
const steps = ["plan", "build"];
steps.length = 5;
console.log(steps.length);
console.log(Object.hasOwn(steps, 2));
console.log(steps[2]);
5
false
undefined
The array now spans five slots, but indexes 2, 3, and 4 are empty. No elements containing undefined were created.
To grow an array without holes, append concrete values instead of increasing length directly:
const steps = ["plan", "build"];
steps.push("test", "review", "ship");
The payoff is direct control over the boundary. The price is that shortening deletes configurable indexed elements and growing introduces holes. A non-configurable indexed property can prevent truncation, leaving the length one greater than the highest index that could not be deleted.
Array Length Mistakes and Edge Cases
The first mistake is calling length: write tasks.length, not tasks.length().
The second mistake is an off-by-one loop. index <= tasks.length runs once beyond the final possible index. Use index < tasks.length.
The third is using delete for ordinary removal:
const tasks = ["plan", "build", "test"];
delete tasks[1];
console.log(tasks.length);
console.log(Object.hasOwn(tasks, 1));
console.log(tasks[1]);
3
false
undefined
The length stays 3, and index 1 becomes a hole. Use splice() when the later elements should shift left and close the gap.
The fourth mistake is expecting new Array(3) to contain three undefined elements:
const slots = new Array(3);
console.log(slots.length);
console.log(Object.hasOwn(slots, 0));
console.log([...slots]);
3
false
[ undefined, undefined, undefined ]
The original array has three holes. Spreading it produces a different array with three explicit undefined elements.
After numeric coercion, an assigned length must equal an integer from 0 through 2^32 - 1. The largest permitted value is 4,294,967,295; other numeric results throw a RangeError:
for (const invalidLength of [-1, 1.5, 4294967296]) {
try {
const values = [];
values.length = invalidLength;
} catch (error) {
console.log(error.name);
}
}
RangeError
RangeError
RangeError
The final mistake is replacing an occupied-index count with Object.keys(array).length. Arrays can have custom enumerable properties that are not array indexes:
const tasks = ["plan", "build"];
tasks.owner = "Maya";
console.log(tasks.length);
console.log(Object.keys(tasks).length);
console.log(Object.keys(tasks).join(", "));
2
3
0, 1, owner
Object.keys() counts "owner" alongside indexes "0" and "1", while length remains 2. When you need an exact occupied-index count, walk from 0 to length - 1 and test each position with Object.hasOwn().
length answers the boundary question. Ask a different question, and use a different count.
Frequently asked questions
How do you get the length of an array in JavaScript?
length property, such as tasks.length. It reports how many indexed slots the array spans, which can be greater than the number of occupied indexes in a sparse array.How do you check whether a JavaScript array is empty?
array.length === 0. This checks whether the array spans zero indexed slots, so it is reliable for ordinary dense arrays but does not tell you whether a sparse array contains useful values.Does deleting an array element change its length?
delete operator removes the element but leaves an empty slot, so the array keeps the same length. A non-configurable element cannot be deleted. Use splice() when later elements should shift left.What happens when you assign a new value to array length?
0 clears those elements. After numeric coercion, a length outside the integer range from 0 through 2^32 - 1 throws a RangeError.