JavaScript Array Length: Count, Check, and Resize

Sep 9, 2026·20 min read

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.length property 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 or undefined matter.

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.

Length is one past the last indexdraftreviewpublishedoutsidethe arrayindex 0index 1index 2index 3length − 1length
With zero-based indexes, the last item is at length minus one.

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.

Same read, different structureindex existsvalue: undefinedindex missingholehasOwn → truehasOwn → falseread →undefined
Stored undefined and a hole read alike, but only one index actually exists.

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.

Four questions, four correct countsslot spancount: 618U12occupiedcount: 3defined valuescount: 2value ≥ 15count: 1all321
Different counting questions select different parts of the same array.

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.

What happens to the hole?AholeCforEach()visits A, Chole skippedfor…ofA, undefined,Cspreadstores anundefined
Array operations do not all treat holes the same way.

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.

QuestionRecipeResult for readings
How many indexed slots does the array span?readings.length6
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).length2
How many visited values match a condition?readings.filter(value => value >= 15).length1

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.

A distant write stretches the arraybefore: length 2MRwrite 5after: length 6MRI012345three holes
Assigning a distant index grows the span and leaves holes in between.

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.

Deleted values do not come backlength 4planbuildtestshipset to 2length 2planbuildtest, shipdeletedset to 4length 4planbuildholeholepositions return; values do not
Shrinking and regrowing an array is not an undo operation.

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.

Removing the middle itemplanbuildtestdelete: length 3planholetestindex 2 stays putsplice: length 2plantesttest shifts to index 1
Delete leaves a hole; splice closes 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().

Two ways to see the same objectObject.keys() sees 0, 1, ownerindex 0planindex 1buildownerMayalength = 2 indexed slotsnamed property
Named array properties appear in Object.keys but do not extend array length.

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?
Read the array's 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?
Use 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?
No. For a configurable indexed property, the 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?
When the affected indexed properties are configurable, a smaller length deletes elements beyond the new boundary, while a larger length adds empty slots. Assigning 0 clears those elements. After numeric coercion, a length outside the integer range from 0 through 2^32 - 1 throws a RangeError.