Sizeof Array in JavaScript: Length vs. Bytes

Aug 31, 2026·21 min read

You have an array and need its size. The answer depends on whether size means index slots, populated entries, selected values, unique values, encoded bytes, or live memory.

For a normal JavaScript array, use array.length to measure its index range; use byteLength for typed-array storage, TextEncoder for a chosen UTF-8 representation, and an engine profiler for live heap memory.

The Short Answer for Slot Count: Use array.length

A normal JavaScript array has a length property. Read it without parentheses:

const emptyTasks = [];
const tasks = ["draft", "review", "publish"];

console.log(emptyTasks.length);
console.log(tasks.length);
0
3

length is a property, not a method. tasks.length returns 3, while tasks.length() attempts to call the number 3 as a function and throws a TypeError.

JavaScript also has no sizeof(array) operator corresponding to the C or C++ operator. These forms do not give you the size of a normal array:

tasks.size
tasks.size()
sizeof(tasks)

tasks.size reads a property that the array does not have, so it produces undefined. Calling tasks.size() or writing sizeof(tasks) fails. Map and Set use size; Array uses length.

In the ordinary case, size means the array’s index range. A dense array containing three values has three occupied index slots and a length of 3.

That answer needs one qualification. length can include empty slots, so it is not always the number of populated entries. Array Size in JavaScript: A Visual Guide covers the basic length patterns; the sections below separate length from the other counts hidden inside the word size.

What Array.length Actually Measures

An array’s length marks its index boundary. It is normally one greater than the highest array index.

Say you store three route names:

const routes = ["home", "account", "settings"];

console.log(routes[0]);
console.log(routes[2]);
console.log(routes.length);
home
settings
3

Array indexes start at 0, so a length of 3 gives the usual indexes 0, 1, and 2. The highest index is 2, and length is one greater.

Indexes stop one step before lengthlength = 3 slotshomeaccountsettingsindex 0index 1index 2boundary 3
Three zero-based indexes end at the boundary called length 3.

Writing beyond the current boundary grows the array automatically:

const scores = [8, 9];
scores[4] = 10;

console.log(scores.length);
console.log(scores[4]);
5
10

Index 4 forces the length to 5. Indexes 2 and 3 were not filled with stored undefined values. They became empty slots, which the next section examines.

A far index stretches the boundarywrite scores[4] = 1089holehole10index 0index 1index 2index 3index 4length becomes 5
Writing index 4 grows the range to five slots while indexes 2 and 3 remain holes.

You can also assign to length directly. Reducing it truncates indexed entries:

const chapters = ["values", "arrays", "objects", "functions"];

chapters.length = 2;

console.log(chapters.length);
console.log(chapters.join(", "));
console.log(chapters[2]);
2
values, arrays
undefined

The entries at indexes 2 and 3 are deleted. The original values do not return if you increase the length again.

Shortening length is a cut, not a curtainBefore: length 4valuesarraysobjectsfunctionsset length = 2After: entries 2 and 3 are gonevaluesarraysholeholeGrowing again creates absence, not restored values
Truncation deletes entries; extending afterward leaves empty slots.

Increasing length extends the boundary with empty slots:

const chapters = ["values", "arrays"];
chapters.length = 5;

console.log(chapters.length);
console.log(Object.keys(chapters).join(", "));
5
0, 1

The array has five slots in its index range, but only two own indexed properties. Its length is 5; its populated-entry count is 2.

An array length is a nonnegative integer below 2^32, so the maximum is 2^32 - 1, or 4294967295. That boundary can describe an enormous sparse range without creating billions of stored values:

const boundary = [];
boundary.length = 4294967295;

console.log(boundary.length);
console.log(Object.keys(boundary).length);
4294967295
0

The length is at its maximum, while the array still has no populated indexes. Array length describes a boundary first.

Sparse Arrays: Length Is Not an Item Count

A sparse array has one or more empty slots inside its index range. An empty slot, often called a hole, means the array has no own property at that index.

That differs from an entry whose value is explicitly undefined. Indexed access produces undefined in both cases, but property checks and array methods can tell them apart.

Same read result, different structureindex 1: holeno property hereindex 2: presentundefinedread → undefinedread → undefined1 in readingsfalse2 in readingstrue
A hole and an explicit undefined look alike when read but differ when checked.

Start with five slots and three present entries:

const readings = [];
readings[0] = 18;
readings[2] = undefined;
readings[4] = 21;

console.log(readings.length);
console.log(readings[1], readings[2]);
console.log(1 in readings, 2 in readings);
console.log(Object.keys(readings).join(", "));
5
undefined undefined
false true
0, 2, 4

Both indexed reads print undefined. The in operator reveals the difference: index 1 is a hole, while index 2 exists and stores undefined. Object.keys() finds the three present indexed properties.

Methods do not all observe holes in the same way. forEach() and filter() skip them:

const readings = [];
readings[0] = 18;
readings[2] = undefined;
readings[4] = 21;

const visits = [];

readings.forEach((value, index) => {
  visits.push(`${index}:${String(value)}`);
});

console.log(visits.join(" | "));
console.log(readings.filter(() => true).length);
0:18 | 2:undefined | 4:21
3

forEach() visits indexes 0, 2, and 4. It skips holes at 1 and 3, but it does visit the explicit undefined at index 2.

For an ordinary sparse array, filter(() => true).length is a convenient populated-entry count. The callback accepts every entry it receives, and filter() never calls it for a hole. This technique is not a universal property-reflection operation for arrays with unusual inherited indexed properties.

Indexed access and for...of instead observe a hole as undefined. Spreading goes one step further by creating explicit undefined entries in the new array:

const sparse = ["north", , "south"];
const dense = [...sparse];

console.log(sparse.length, Object.keys(sparse).length);
console.log(dense.length, Object.keys(dense).length);
console.log(1 in sparse, 1 in dense);
console.log(dense[1]);
3 2
3 3
false true
undefined

Both arrays have length 3. The sparse original has two present indexes, while the spread copy has three. Spreading turned the hole at index 1 into an entry containing undefined.

Spread fills the structural gapSparse originalnorthholesouth[…sparse]Dense copynorthundefinedsouthlength 3 → length 3, but 2 entries → 3 entries
Spread keeps the slot range but changes the middle slot from absence to a stored value.

That distinction also matters when you count in JavaScript. A slot count, a present-entry count, and a count of values satisfying a condition answer different questions, even when they happen to return the same number.

Choose the Count You Actually Need

The word count hides four separate goals. Name the goal before choosing the expression.

  • array.length measures the index range, including holes.
  • array.filter(() => true).length counts present entries in an ordinary sparse array.
  • array.filter(predicate).length counts present values accepted by a condition.
  • new Set(array).size counts unique values produced by array iteration.

Here are the four measurements against concrete data:

const temperatures = [18, , 18, undefined, 23, 23];

const slots = temperatures.length;
const present = temperatures.filter(() => true).length;
const numeric = temperatures.filter(value => typeof value === "number").length;
const uniquePresent = new Set(temperatures.filter(() => true)).size;

console.log(slots);
console.log(present);
console.log(numeric);
console.log(uniquePresent);
6
5
4
3

slots is 6 because the highest index is 5. present is 5 because the hole at index 1 is skipped, while explicit undefined remains an entry. numeric is 4, counting two 18 values and two 23 values. uniquePresent is 3, representing 18, undefined, and 23.

One array, several legitimate countsdata18hole18un-def2323slots: 6present: 5numbers: 4unique: 318undefined23Duplicates merge only for the unique-value question
Different questions illuminate different positions in the same six-slot array.

If you write new Set(temperatures).size without filtering first, array iteration observes the hole as undefined. In this example that still produces three unique values because an explicit undefined is already present. Without that explicit entry, the hole would introduce undefined into the Set.

A predicate can express any matching rule. Say you need the number of completed tasks:

const tasks = [
  { title: "draft", done: true },
  { title: "review", done: false },
  { title: "publish", done: true },
];

const completed = tasks.filter(task => task.done).length;

console.log(completed);
2

length answers how many task objects the filtered array contains. The predicate decides which original values qualify. The same pattern works with the JavaScript forEach loop when you need side effects, but filter() states a counting condition more directly.

For very large arrays, a reduce() counter avoids creating the filtered result:

const scores = [72, 91, 64, 88];

const passing = scores.reduce(
  (count, score) => count + (score >= 70 ? 1 : 0),
  0
);

console.log(passing);
3

The count begins at 0 and increases only when a score is at least 70. This is a matching-value count, not a slot count.

How to Measure an Array in Bytes

Bytes can mean typed-array storage, a serialized payload, or live heap memory. Those measurements are not interchangeable.

Typed-array storage with byteLength

A typed array stores values using a fixed element type. Its length reports elements, while its byteLength reports the bytes covered by that view.

Here is a Uint16Array with three elements:

const samples = new Uint16Array([500, 1000, 1500]);

console.log(samples.length);
console.log(samples.byteLength);
console.log(Uint16Array.BYTES_PER_ELEMENT);
3
6
2

The view contains 3 elements. Each Uint16Array element uses 2 bytes, so the view covers 6 bytes. BYTES_PER_ELEMENT gives the byte width for that typed-array type.

Elements and bytes are two scaleslength = 3 elements50010001500B1B2B1B2B1B2byteLength = 6 bytesBYTES_PER_ELEMENT = 2
Three Uint16 elements occupy two bytes each, for six viewed bytes total.

A typed-array view can cover part of an ArrayBuffer. In that case, view.byteLength measures the bytes covered by the view, not every byte in the backing buffer.

Serialized UTF-8 bytes with TextEncoder

A normal array has no portable storage-byte property. If the array will cross a network or enter a file, first choose a representation and an encoding.

The next example chooses JSON serialization and UTF-8 encoding:

const labels = ["café", "東京"];
const json = JSON.stringify(labels);
const utf8 = new TextEncoder().encode(json);

console.log(json);
console.log(json.length);
console.log(utf8.byteLength);
["café","東京"]
13
18

json.length is the JavaScript string length, measured in UTF-16 code units. It is not a byte count. TextEncoder converts that exact JSON string to UTF-8 bytes, and the resulting Uint8Array reports 18 through byteLength.

This number belongs to the chosen representation. Changing the serialization, whitespace, property content, or encoding can change it. JSON serialization also transforms or rejects some JavaScript values, so it is not a neutral view of everything stored in an array.

Bytes belong to a chosen representationarray valuesJSON.stringify[“café”,“東京”]13 UTF-16 code unitsTextEncoder18 UTF-8 bytes
JSON and UTF-8 form a measurement pipeline; the final byte count describes its output.

Serialized byte size measures the payload you created, not the memory occupied by the live array.

Live heap memory needs profiling

Ordinary arrays can hold numbers, strings, objects, holes, and references to values stored elsewhere. Their internal representation and memory cost can vary with the JavaScript engine, the values, sparseness, and optimization state.

There is no portable JavaScript expression equivalent to sizeof(array) that returns that live footprint. Use the memory or heap profiler provided by the runtime or browser, measure before and after a controlled allocation, and inspect retained objects when shared references matter.

A profiler answers an engine-specific runtime question. length, byteLength, and UTF-8 payload size cannot replace it. Object Layout and What Memory Really Costs explains why object shape and stored values complicate memory accounting, while Under the Hood follows the engine behavior across the wider course.

Array Size Mistakes to Avoid

Choose the expression from the question you need to answer:

  • Use array.length, not array.size, for a normal array’s index range.
  • Read array.length as a property. Do not call array.length().
  • Do not treat a hole as an explicit undefined entry.
  • Use filter(() => true).length only when you want present entries in an ordinary sparse array.
  • Use filter(predicate).length or a reduce() counter for matching values.
  • Use new Set(array).size for unique iterated values, and filter holes first when only present entries should participate.
  • Do not assign a smaller length unless truncating the array is intentional.
  • For a typed array, use length for elements and byteLength for viewed bytes.
  • For a payload, name the serialization and encoding before measuring bytes.
  • For live heap memory, use an engine profiler rather than a guessed per-element formula.

The complete set of code-level measurements fits into one example:

const values = [18, , 18, undefined, 23];
const typedValues = new Uint16Array([18, 18, 23]);

const slots = values.length;
const presentValues = values.filter(() => true);
const matches = values.filter(value => typeof value === "number");
const uniquePresent = new Set(presentValues);
const jsonBytes = new TextEncoder().encode(
  JSON.stringify(presentValues)
).byteLength;

console.log("slots:", slots);
console.log("present:", presentValues.length);
console.log("numeric matches:", matches.length);
console.log("unique present:", uniquePresent.size);
console.log("typed elements:", typedValues.length);
console.log("typed bytes:", typedValues.byteLength);
console.log("JSON UTF-8 bytes:", jsonBytes);
slots: 5
present: 4
numeric matches: 3
unique present: 3
typed elements: 3
typed bytes: 6
JSON UTF-8 bytes: 15

The array has five slots, four present entries, three numeric matches, and three unique present values. The typed array has three elements covering six bytes, while the JSON UTF-8 measurement belongs only to the serialized presentValues payload. Live heap memory remains a profiling job.

Frequently asked questions

How do I get the size of an array in JavaScript?
Read the array's length property, such as items.length. It reports the array's index range, which can exceed the number of populated entries when the array contains empty slots.
Does JavaScript have a sizeof operator for arrays?
No. JavaScript has no sizeof operator corresponding to the C or C++ operator. Use length for array slots, byteLength for a typed-array view, or an engine profiler for live heap memory.
What is the difference between array length and byteLength?
For a typed array, length is the number of elements and byteLength is the number of bytes covered by the view. Normal arrays have length but no byteLength property.
How do I count populated entries in a sparse JavaScript array?
For a sparse array with no inherited indexed properties, array.filter(() => true).length counts own present entries because filter skips empty slots. It still counts entries whose stored value is undefined.
How do I measure the UTF-8 size of a JavaScript array?
First choose a serialization, such as JSON.stringify(array), then encode that string with TextEncoder. The resulting Uint8Array.byteLength measures the UTF-8 representation, not the array's live heap memory.