Sizeof Array in JavaScript: Length vs. Bytes
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.lengthto measure its index range; usebyteLengthfor typed-array storage,TextEncoderfor 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.
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.
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.
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.
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.
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.lengthmeasures the index range, including holes.array.filter(() => true).lengthcounts present entries in an ordinary sparse array.array.filter(predicate).lengthcounts present values accepted by a condition.new Set(array).sizecounts 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.
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.
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.
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, notarray.size, for a normal array’s index range. - Read
array.lengthas a property. Do not callarray.length(). - Do not treat a hole as an explicit
undefinedentry. - Use
filter(() => true).lengthonly when you want present entries in an ordinary sparse array. - Use
filter(predicate).lengthor areduce()counter for matching values. - Use
new Set(array).sizefor unique iterated values, and filter holes first when only present entries should participate. - Do not assign a smaller
lengthunless truncating the array is intentional. - For a typed array, use
lengthfor elements andbyteLengthfor 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.